mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-10-03 18:23:47 +10:00
- Add a Questions section to the homepage: eight answers as folded paper notes that unfold from their crease, with a pencil circle and underline drawn on open, a new doodle, and FAQPage structured data rendered from the same localized answers. - Prerender /ats-checker per locale alongside the homepage (dist-prerender/<page>/<locale>.html) and add a "What it checks" section; PDF.js and the importers now load only once a file is chosen. - Make the server the single owner of SEO head tags: canonical, hreflang, social cards with og:locale and the page's localized title, and JSON-LD with an Organization entity. The router now sets only the title, description and robots tags, so nothing is duplicated after startup and the ATS checker keeps its structured data. - Add hreflang alternates to the sitemap, expand llms.txt, and delete the stale v5 robots.txt and sitemap.xml from public/. - Draw decorative heading and typed-text layers as generated content, so page text holds each heading once.
111 lines
4.5 KiB
TypeScript
111 lines
4.5 KiB
TypeScript
import { env } from "@reactive-resume/env/server";
|
|
import { templateSchema } from "@reactive-resume/schema/templates";
|
|
import { getLocaleAlternates } from "@reactive-resume/utils/locale";
|
|
|
|
const DOCS_URL = "https://docs.rxresu.me";
|
|
|
|
type StaticSeoOptions = {
|
|
head?: boolean;
|
|
};
|
|
|
|
function appUrl() {
|
|
return env.APP_URL.replace(/\/+$/, "");
|
|
}
|
|
|
|
function textResponse(body: string, options: StaticSeoOptions = {}) {
|
|
return new Response(options.head ? null : body, {
|
|
headers: { "Content-Type": "text/plain; charset=UTF-8" },
|
|
});
|
|
}
|
|
|
|
export function handleRobots(options?: StaticSeoOptions) {
|
|
const baseUrl = appUrl();
|
|
const body = [
|
|
"User-agent: *",
|
|
"Allow: /",
|
|
"Disallow: /api/rpc",
|
|
"Disallow: /api/auth",
|
|
"Disallow: /mcp",
|
|
"Disallow: /.well-known",
|
|
"",
|
|
`Sitemap: ${baseUrl}/sitemap.xml`,
|
|
`Sitemap: ${DOCS_URL}/sitemap.xml`,
|
|
"",
|
|
].join("\n");
|
|
|
|
return textResponse(body, options);
|
|
}
|
|
|
|
// The indexable pages; everything else is the signed-in app or a public resume, both served noindex.
|
|
const sitemapPaths = ["/", "/ats-checker"];
|
|
|
|
export function handleSitemap(options?: StaticSeoOptions) {
|
|
const baseUrl = appUrl();
|
|
// Each page lists its languages, so every `?locale=` address is discoverable without a sitemap entry of its own.
|
|
const urls = sitemapPaths.map((path) => {
|
|
const pageUrl = `${baseUrl}${path}`;
|
|
const alternates = getLocaleAlternates(pageUrl).map(
|
|
({ hreflang, href }) =>
|
|
` <xhtml:link rel="alternate" hreflang="${hreflang}" href="${href.replaceAll("&", "&")}"/>`,
|
|
);
|
|
return [" <url>", ` <loc>${pageUrl}</loc>`, ...alternates, " </url>"].join("\n");
|
|
});
|
|
const body = [
|
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">',
|
|
...urls,
|
|
"</urlset>",
|
|
"",
|
|
].join("\n");
|
|
|
|
return new Response(options?.head ? null : body, {
|
|
headers: { "Content-Type": "application/xml; charset=UTF-8" },
|
|
});
|
|
}
|
|
|
|
export function handleLlms(options?: StaticSeoOptions) {
|
|
const baseUrl = appUrl();
|
|
const body = [
|
|
"# Reactive Resume",
|
|
"",
|
|
`> Reactive Resume is a free and open-source resume builder. Write a resume in an editor beside a live page, pick one of ${templateSchema.options.length} templates, check that applicant tracking systems can read it, tailor it to a job posting, and share it at a public link or download it. No ads, no tracking, no paid tier; it is funded by donations and released under the MIT License.`,
|
|
"",
|
|
"## Product",
|
|
"",
|
|
`- [Homepage](${baseUrl}/): what Reactive Resume does, with answers to common questions.`,
|
|
`- [ATS checker](${baseUrl}/ats-checker): a free tool that shows the text applicant tracking systems extract from a resume PDF and what to fix. It runs in the browser; the file is never uploaded.`,
|
|
`- [Get started](${baseUrl}/dashboard): create an account and a first resume.`,
|
|
"",
|
|
"## Facts",
|
|
"",
|
|
"- Price: free, every feature. Optional donations through GitHub Sponsors and Open Collective.",
|
|
"- Export: PDF, Word (DOCX), Markdown and JSON.",
|
|
"- Import: PDF, LinkedIn data export, JSON Resume, Reactive Resume JSON; Word files with an AI provider.",
|
|
"- Sharing: private by default; a public link or a password-protected link, changeable at any time.",
|
|
"- AI: optional, with the user's own API key (OpenAI, Anthropic, Google Gemini, OpenRouter, Ollama and others). Nothing is sent to an AI service without one.",
|
|
"- Also includes: cover letters, a job application tracker, version history, passkeys and two-factor authentication.",
|
|
"- Languages: the interface is translated into more than 50 languages by volunteers on Crowdin.",
|
|
"- Self-hosting: a Docker image, with PostgreSQL and local or S3-compatible storage.",
|
|
"",
|
|
"## Documentation",
|
|
"",
|
|
`- [Documentation](${DOCS_URL}): guides for using and self-hosting Reactive Resume.`,
|
|
`- [Documentation llms.txt](${DOCS_URL}/llms.txt)`,
|
|
`- [Self-hosting with Docker](${DOCS_URL}/self-hosting/docker)`,
|
|
`- [API reference](${DOCS_URL}/api-reference)`,
|
|
`- [OpenAPI specification](${baseUrl}/api/openapi/spec.json)`,
|
|
`- [Resume JSON schema](${baseUrl}/schema.json)`,
|
|
`- [MCP server guide](${DOCS_URL}/guides/using-the-mcp-server)`,
|
|
"",
|
|
"## Community",
|
|
"",
|
|
"- [Source code on GitHub](https://github.com/reactive-resume/reactive-resume)",
|
|
"- [Discord](https://discord.gg/aSyA5ZSxpb)",
|
|
"- [Subreddit](https://www.reddit.com/r/reactiveresume)",
|
|
"- [Translations on Crowdin](https://crowdin.com/project/reactive-resume)",
|
|
"",
|
|
].join("\n");
|
|
|
|
return textResponse(body, options);
|
|
}
|