From 917464e50f300c1249c305475ac7ce939529f9bc Mon Sep 17 00:00:00 2001 From: Holger Drewes <931137+holgerd77@users.noreply.github.com> Date: Mon, 15 Jun 2026 14:02:59 +0200 Subject: [PATCH 1/2] Some SEO --- index.html | 1 + scripts/generate-spa-fallbacks.ts | 67 +++- src/App.vue | 4 +- src/components/BreadcrumbNav.vue | 29 ++ .../bytecodeStepperEC/BytecodeStepperEC.vue | 7 +- .../PrecompileInterfaceEC.vue | 1 + src/explorations/ExplorationC.vue | 38 +- src/explorations/eip-7594/MyC.vue | 2 +- src/libs/__tests__/applyPageSeo.spec.ts | 44 +++ src/libs/__tests__/pageSeo.spec.ts | 110 ++++++ src/libs/__tests__/spaRoutes.spec.ts | 30 +- src/libs/applyPageSeo.ts | 81 ++++ src/libs/pageSeo.ts | 361 ++++++++++++++++++ src/libs/spaRoutes.ts | 65 +--- src/router/index.ts | 6 + src/views/ExplorationView.vue | 4 + src/views/HomeView.vue | 1 + src/views/ImprintView.vue | 9 +- src/views/TopicIntroView.vue | 10 +- src/views/TopicView.vue | 6 + 20 files changed, 782 insertions(+), 94 deletions(-) create mode 100644 src/components/BreadcrumbNav.vue create mode 100644 src/libs/__tests__/applyPageSeo.spec.ts create mode 100644 src/libs/__tests__/pageSeo.spec.ts create mode 100644 src/libs/applyPageSeo.ts create mode 100644 src/libs/pageSeo.ts diff --git a/index.html b/index.html index 70652dc..a9b7a04 100644 --- a/index.html +++ b/index.html @@ -4,6 +4,7 @@ + Feel Your Protocol diff --git a/scripts/generate-spa-fallbacks.ts b/scripts/generate-spa-fallbacks.ts index eed824e..17344e8 100644 --- a/scripts/generate-spa-fallbacks.ts +++ b/scripts/generate-spa-fallbacks.ts @@ -4,35 +4,80 @@ * nginx serves static files with `try_files $uri $uri/ =404` (no blanket index.html * fallback). This script materializes what nginx needs: * - * - `scaling/index.html`, … — one copy per valid SPA route so deep links return 200 - * - `404.html` — same app shell; Vue Router shows NotFoundView for unknown paths - * - `sitemap.xml` and `robots.txt` — canonical URLs from `src/libs/spaRoutes.ts` + * - Per-route `index.html` with injected title, meta, canonical, Open Graph, JSON-LD + * - `404.html` — same app shell with noindex meta + * - `sitemap.xml` and `robots.txt` * - * Route logic lives in spaRoutes.ts (testable, no filesystem I/O); this file only writes files. + * Route logic lives in `pageSeo.ts` (testable); this file handles filesystem writes. */ -import { copyFileSync, mkdirSync, writeFileSync } from 'node:fs' +import { mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { EXPLORATIONS } from '../src/explorations/REGISTRY' +import { TOPICS } from '../src/explorations/TOPICS' import { generateRobotsTxt, generateSitemapXml, + getPageSeoForPath, getSpaFallbackDirectories, -} from '../src/libs/spaRoutes' + getValidSpaPaths, + injectSeoIntoHtml, +} from '../src/libs/pageSeo' -const outDir = join(dirname(fileURLToPath(import.meta.url)), '../dist/website') +const scriptDir = dirname(fileURLToPath(import.meta.url)) +const websiteRoot = join(scriptDir, '..') +const outDir = join(websiteRoot, 'dist/website') const indexPath = join(outDir, 'index.html') +function toLastmod(date: Date): string { + return date.toISOString().slice(0, 10) +} + +function lastmodForPath(path: string): string { + if (path === '/' || path === '/all') { + return toLastmod(statSync(join(websiteRoot, 'package.json')).mtime) + } + + if (path === '/imprint') { + return toLastmod(statSync(join(websiteRoot, 'src/views/ImprintView.vue')).mtime) + } + + const exploration = Object.values(EXPLORATIONS).find((entry) => entry.path === path) + if (exploration) { + return toLastmod(statSync(join(websiteRoot, 'src/explorations', exploration.id, 'info.ts')).mtime) + } + + const topic = Object.values(TOPICS).find((entry) => entry.path === path) + if (topic && topic.explorations.length > 0) { + const mtimes = topic.explorations.map((id) => + statSync(join(websiteRoot, 'src/explorations', id, 'info.ts')).mtimeMs, + ) + return toLastmod(new Date(Math.max(...mtimes))) + } + + return toLastmod(statSync(join(websiteRoot, 'package.json')).mtime) +} + +const shellHtml = readFileSync(indexPath, 'utf8') +const lastmodByPath = Object.fromEntries(getValidSpaPaths().map((path) => [path, lastmodForPath(path)])) + +writeFileSync(indexPath, injectSeoIntoHtml(shellHtml, getPageSeoForPath('/'))) + for (const dir of getSpaFallbackDirectories()) { + const path = `/${dir}` const targetDir = join(outDir, dir) mkdirSync(targetDir, { recursive: true }) - copyFileSync(indexPath, join(targetDir, 'index.html')) + writeFileSync(join(targetDir, 'index.html'), injectSeoIntoHtml(shellHtml, getPageSeoForPath(path))) } -copyFileSync(indexPath, join(outDir, '404.html')) -writeFileSync(join(outDir, 'sitemap.xml'), generateSitemapXml()) +writeFileSync( + join(outDir, '404.html'), + injectSeoIntoHtml(shellHtml, { ...getPageSeoForPath('/404-not-found'), noindex: true }), +) +writeFileSync(join(outDir, 'sitemap.xml'), generateSitemapXml(lastmodByPath)) writeFileSync(join(outDir, 'robots.txt'), generateRobotsTxt()) console.log( - `Wrote ${getSpaFallbackDirectories().length} SPA fallbacks, 404.html, sitemap.xml, robots.txt`, + `Wrote ${getSpaFallbackDirectories().length + 1} SEO HTML shells, 404.html, sitemap.xml, robots.txt`, ) diff --git a/src/App.vue b/src/App.vue index 6fdf0a3..763abe5 100644 --- a/src/App.vue +++ b/src/App.vue @@ -37,13 +37,13 @@ watch(