From 9d3be71469b810dd5ec679a157e539abadd71682 Mon Sep 17 00:00:00 2001 From: King Omar Date: Wed, 22 Jul 2026 19:49:51 +1000 Subject: [PATCH] =?UTF-8?q?Frontend:=20SEO=20=E2=80=94=20sitemap,=20robots?= =?UTF-8?q?,=20meta,=20OG=20cards,=20SearchAction=20JSON-LD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /sitemap.xml + /robots.txt (homepage indexable; /search + /api excluded) - Full head meta: description, canonical, theme-color - Open Graph + Twitter summary_large_image cards + /og.svg share image - WebSite + SearchAction JSON-LD on the homepage (Google sitelinks search box) - Search-result pages marked noindex,follow per Google guidance Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/src/index.ts | 89 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/frontend/src/index.ts b/frontend/src/index.ts index 2534c61..213ed1f 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -129,6 +129,9 @@ const THEME_JS = ` }); ` +const SITE = 'https://search.theradicalparty.com' +const DESCRIPTION = 'RADICAL_SEARCH — an independent, self-hosted search engine for the open web. Unfiltered results, AI-powered answers, no tracking and no filter bubble.' + // On-brand favicon: dark tile, white "R", accent-pink underscore (the logo mark). const FAVICON = ` @@ -136,15 +139,59 @@ const FAVICON = ` ` -function layout(title: string, body: string) { +// 1200x630 social share card (Open Graph / Twitter). +const OG_IMAGE = ` + + RADICAL_SEARCH + + THE OPEN WEB — UNFILTERED +` + +// WebSite + SearchAction structured data → eligible for a Google sitelinks +// search box. Only emitted on the indexable homepage. +const JSON_LD = `` + +interface SeoOpts { canonical?: string; description?: string; noindex?: boolean; jsonLd?: boolean } + +function layout(title: string, body: string, opts: SeoOpts = {}) { + const desc = opts.description ?? DESCRIPTION + const canonical = opts.canonical ?? SITE + '/' + const robots = opts.noindex ? 'noindex, follow' : 'index, follow' return ` ${title} + + + + + + + + + + + + + + + ${opts.jsonLd ? JSON_LD : ''} ${body} @@ -163,7 +210,7 @@ function homePage(q = '') { - `) + `, { jsonLd: true, canonical: SITE + '/' }) } function resultsPage(q: string, data: any, page: number) { @@ -238,7 +285,7 @@ function resultsPage(q: string, data: any, page: number) { ${(data.nodeId ?? 'main')} node - `) + `, { noindex: true, canonical: `${SITE}/search?q=${encodeURIComponent(q)}`, description: `Search results for "${q}" on RADICAL_SEARCH — the independent open-web search engine.` }) } function esc(s: string) { @@ -258,6 +305,42 @@ export default { }) } + if (url.pathname === '/og.svg') { + return new Response(OG_IMAGE, { + headers: { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'public, max-age=86400' }, + }) + } + + if (url.pathname === '/robots.txt') { + // Allow the homepage; keep dynamic search-result and API URLs out of the + // crawl (Google discourages indexing internal search results, and this + // avoids wasting crawl budget on an unbounded query space). + const body = `User-agent: * +Allow: /$ +Disallow: /search +Disallow: /api/ + +Sitemap: ${SITE}/sitemap.xml +` + return new Response(body, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } }) + } + + if (url.pathname === '/sitemap.xml') { + const body = ` + + + ${SITE}/ + 2026-07-22 + daily + 1.0 + + +` + return new Response(body, { + headers: { 'Content-Type': 'application/xml; charset=utf-8', 'Cache-Control': 'public, max-age=3600' }, + }) + } + if (url.pathname === '/') { // Accept a query on the root too (e.g. /?q=what) — redirect to the // canonical results URL so the search actually runs.