replaceRootSeo() swaps the static #root-seo fallback for the page's SSR
body by string search. The old regex required a <script> right after the
block, but Vite hoists the module script into <head> in the built
index.html, so it never matched: every prerendered page (landings, /v3,
/about, all 171 example pages) shipped the HOMEPAGE's fallback body —
same <h1>, same nav — under its own <head>. Two hundred near-duplicate
bodies, and none of the landings' actual content (h1, sections, FAQ)
ever reached the crawler.
ExamplesGallery passes a server snapshot to useSyncExternalStore so
/examples SSRs instead of falling back to meta-only.
data/starters.ts maps eight free boards to the gallery example that opens
the editor with the board and a working blink (blink-led, nano-blink,
mega-blink, esp32-blink-led, esp32s3-blink-led, c3-blink, pico-blink,
attiny85-blink). ExampleEditorPage gives those ids an action-shaped
title/description ("New ESP32 project — free online ESP32 simulator")
while the gallery keeps the sketch title; the prerender writes
dist/starters.json so the prod app shell can serve the same head in the
raw HTML. This is the Wokwi "New ESP32 Project" shape: a URL of its own,
canonical of its own, that lands inside the product with the board loaded
— a candidate for brand sitelinks with usage intent.
Superseded release pages that were taking two brand sitelink slots (5.9k
impressions, 29 clicks). They still render for old links; hreflang is
only emitted for a page that is its own canonical (prerender + useSEO).
So the prod app shell can give a route that has metadata but (yet) no
prerendered file its own head, self canonical and hreflang instead of the
homepage's.
- useSEO: for the static SEO routes (marketing pages, examples gallery,
editor, home) the canonical is now the page's OWN locale variant and the
head lists every locale variant as rel=alternate hreflang plus x-default
on the locale-less English URL. /es/ and /zh-cn/ were declaring
canonical=/ while Google indexed and ranked them on their own — an
unstable contradiction. UGC pages (projects, profiles, example detail
pages) are the same English content under every prefix and keep a single
locale-less canonical with no hreflang.
- prerender-seo.mjs emits the same hreflang set into each prerendered page
and writes dist/seo-routes.json (path/title/description) so the prod app
shell can serve the /<locale>/<route> variants (which have no static file)
with the route's own head.
- /editor gets seoMeta and a static SSR summary component, so it is
prerendered: nginx now serves /editor/ as a real page and 301s /editor to
it. Both forms answered 200 with the homepage head before, and Google
indexed /editor and /editor/ as two pages.
The open-source build now ships exactly its product surface: /editor, the
examples gallery (/examples, /examples/:id) and the per-example editor
(/example/:id). Landing, about, pricing, docs, the 14 keyword-targeted
simulator landings and the v2/v2.5/v3 showcases move to the private
overlay, registered through the same registerProRoutes seam that already
carries login/admin/classroom.
Root behaves per build: an overlay that registers an index route claims
'/' (velxio.dev keeps its landing); otherwise '/' redirects to /editor.
The redirect waits for the overlay import to settle — same contract as
markProExamplesSettled — so a velxio.dev visitor is never bounced into
the editor because the landing was 300ms away from registering.
Prerender and sitemap follow the split: entry-server pulls the marketing
page map from '@pro/pages/marketing' behind a VITE_PRO_BUILD-gated dynamic
import (the proven main.tsx pattern, with an OSS stub for tsc), and
generate-sitemap lists only served routes in OSS builds (2 URLs) while
pro builds stay byte-identical — verified against a pre-migration
baseline: sitemap (37 URLs) and prerendered /, /about, /docs,
/arduino-simulator, /esp32-simulator, /v3 all identical modulo hashed
asset names. OSS prerender drops exactly the 32 marketing pages (348→316).
The OSS header slims down to match: Editor, Examples, GitHub, Discord —
the marketing links only render in pro builds, where their routes exist.
The editor Help menu links those pages absolutely (velxio.dev) in OSS,
exactly like the desktop app's Help menu.
Static/docs/example routes are served as <route>/index.html and nginx
301-redirects the slash-less form to add the trailing slash. The sitemap
generator, the prerender canonical/og:url, and the client useSEO canonical
all emitted the slash-LESS form, so every sitemap URL was fetched as a
redirect (filed under 'Page with redirect' in Search Console) and each
canonical pointed at a redirecting URL.
Align all three to the trailing-slash form so sitemap URL == canonical ==
served URL == 200, with no redirect hop.
- generate-sitemap.mjs: append '/' to every <loc> (root stays '/')
- prerender-seo.mjs: withSlash() on canonical + og:url (routes + examples)
- useSEO.ts: withTrailingSlash() on canonical + og:url (covers dynamic
project pages too)
- Implemented ExampleDetailPage for individual example projects with SEO metadata.
- Updated routing to use ExampleDetailPage instead of ExampleLoaderPage.
- Enhanced sitemap generation to include example project URLs.
- Added prerendering support for example detail pages in the server entry.
- Improved SEO handling in ProjectByIdPage to dynamically set metadata based on project visibility.
- Refactored example ID extraction from examples.ts for sitemap generation.
- Updated console logs to reflect total URLs generated in sitemap.