diff --git a/apps/server/src/static/web.test.ts b/apps/server/src/static/web.test.ts
index 419b735d8..02013cae7 100644
--- a/apps/server/src/static/web.test.ts
+++ b/apps/server/src/static/web.test.ts
@@ -174,6 +174,21 @@ describe("web app fallback classification", () => {
expect(html).toContain('content="Ends with " and & ampersand"');
});
+ it("keeps replacement patterns in resume text literal", async () => {
+ vi.mocked(fs.readFile).mockResolvedValue(shell);
+ mocks.getPublicResumeSocialMeta.mockResolvedValue({
+ name: "Jane $& $' Doe",
+ title: "Jane Doe",
+ description: "Costs $$ and $` nothing",
+ template: "azurill",
+ });
+
+ const html = await (await handleWebApp(new Request("https://example.com/jane/resume"))).text();
+
+ expect(html).toContain("
Jane $& $' Doe - Reactive Resume");
+ expect(html).toContain('');
+ });
+
it("serves the plain shell when the resume is not publicly shareable", async () => {
vi.mocked(fs.readFile).mockResolvedValue(shell);
diff --git a/apps/server/src/static/web.ts b/apps/server/src/static/web.ts
index b50676732..bb337f134 100644
--- a/apps/server/src/static/web.ts
+++ b/apps/server/src/static/web.ts
@@ -315,11 +315,17 @@ export async function handleWebApp(request: Request) {
.replace(/[^<]*<\/title>/, "Reactive Resume")
.replace(/]*>/, '');
const markup = ``;
- return new Response(shell.replace("", `${markup}`), { headers });
+ return new Response(
+ shell.replace("", () => `${markup}`),
+ { headers },
+ );
}
if (pathname === "/") {
- return new Response(html.replace("", `${createRootSeoMarkup(canonicalUrl)}`), { headers });
+ return new Response(
+ html.replace("", () => `${createRootSeoMarkup(canonicalUrl)}`),
+ { headers },
+ );
}
if (pathname === "/ats-checker") {
@@ -328,19 +334,28 @@ export async function handleWebApp(request: Request) {
.replace(/[^<]*<\/title>/, `${ATS_CHECKER_TITLE}`)
.replace(/]*>/, ``);
- return new Response(withTitle.replace("", `${createAtsCheckerSeoMarkup(origin)}`), { headers });
+ return new Response(
+ withTitle.replace("", () => `${createAtsCheckerSeoMarkup(origin)}`),
+ { headers },
+ );
}
if (isPublicResumePath(pathname)) {
const resumeSeo = await createPublicResumeSeoMarkup(pathname, new URL(env.APP_URL).origin);
if (resumeSeo) {
// The shell's generic title/description are replaced so shares and previews show the resume,
- // not the marketing copy baked into index.html.
+ // not the marketing copy baked into index.html. Function replacers keep `$&`, `$'` etc. in user text literal.
const withTitle = html
- .replace(/[^<]*<\/title>/, `${resumeSeo.pageTitle}`)
- .replace(/]*>/, ``);
+ .replace(/[^<]*<\/title>/, () => `${resumeSeo.pageTitle}`)
+ .replace(
+ /]*>/,
+ () => ``,
+ );
- return new Response(withTitle.replace("", `${resumeSeo.markup}`), { headers });
+ return new Response(
+ withTitle.replace("", () => `${resumeSeo.markup}`),
+ { headers },
+ );
}
}