2026-03-25 10:08:46 +07:00
|
|
|
/**
|
|
|
|
|
* SSR entry point for prerendering SEO pages at build time.
|
|
|
|
|
*
|
|
|
|
|
* Used by scripts/prerender-seo.mjs via Vite's ssrLoadModule.
|
|
|
|
|
* Renders each page component to an HTML string so the prerender script
|
|
|
|
|
* can inject it into the static dist/index.html per route.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import { renderToString } from 'react-dom/server';
|
2026-08-18 23:35:01 +07:00
|
|
|
// Initialise i18next with the bundled English resources before any page
|
|
|
|
|
// renders: without this every t() call in the SSR output came back as its
|
|
|
|
|
// KEY ("landing.hero.titleLine1"), which is what the prerendered bodies
|
|
|
|
|
// shipped once they were actually injected.
|
|
|
|
|
import './i18n';
|
|
|
|
|
import { MemoryRouter, Routes, Route } from 'react-router-dom';
|
2026-03-25 10:08:46 +07:00
|
|
|
import { SEO_ROUTES } from './seoRoutes';
|
|
|
|
|
|
|
|
|
|
// ── SEO page components ─────────────────────────────────────────────────────
|
2026-04-14 01:22:08 +07:00
|
|
|
import { exampleProjects } from './data/examples';
|
2026-03-25 10:08:46 +07:00
|
|
|
import { ExamplesPage } from './pages/ExamplesPage';
|
2026-04-14 01:22:08 +07:00
|
|
|
import { ExampleDetailPage } from './pages/ExampleDetailPage';
|
2026-03-25 10:08:46 +07:00
|
|
|
|
refactor(routes): OSS is the editor; marketing moves to the overlay
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.
2026-07-30 22:50:14 +07:00
|
|
|
// Map route paths to their React component. The OSS build prerenders only
|
|
|
|
|
// what it ships (the examples gallery); the marketing surface lives in the
|
|
|
|
|
// pro overlay and is merged in by loadRouteComponents() below.
|
2026-08-18 21:50:39 +07:00
|
|
|
/**
|
|
|
|
|
* /editor is the live workspace (Monaco, WASM engines) — nothing there
|
|
|
|
|
* survives renderToString, so its prerender is a static crawlable summary.
|
|
|
|
|
* Being prerendered at all is what matters: nginx then serves /editor/ as
|
|
|
|
|
* a real page and 301s /editor to it, instead of both forms answering 200
|
|
|
|
|
* with the homepage head (Google had indexed the two as separate pages).
|
|
|
|
|
*/
|
|
|
|
|
const EditorSeoSummary: React.FC = () => (
|
|
|
|
|
<main>
|
|
|
|
|
<h1>Velxio Editor — multi-board circuit and code simulator</h1>
|
|
|
|
|
<p>
|
|
|
|
|
Write, compile and simulate Arduino, ESP32, ESP32-C3, ESP32-S3, Raspberry Pi
|
|
|
|
|
Pico and Raspberry Pi code in your browser, wired to a live circuit canvas
|
|
|
|
|
with SPICE analog simulation. Free, open source, no install and no account
|
|
|
|
|
needed.
|
|
|
|
|
</p>
|
|
|
|
|
</main>
|
|
|
|
|
);
|
|
|
|
|
|
refactor(routes): OSS is the editor; marketing moves to the overlay
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.
2026-07-30 22:50:14 +07:00
|
|
|
const OSS_ROUTE_COMPONENTS: Record<string, React.FC> = {
|
2026-08-18 21:50:39 +07:00
|
|
|
'/editor': EditorSeoSummary,
|
2026-03-25 10:08:46 +07:00
|
|
|
'/examples': ExamplesPage,
|
|
|
|
|
};
|
|
|
|
|
|
refactor(routes): OSS is the editor; marketing moves to the overlay
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.
2026-07-30 22:50:14 +07:00
|
|
|
let routeComponents: Record<string, React.FC> = OSS_ROUTE_COMPONENTS;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Merge in the overlay's marketing pages (landing, about, docs, the SEO
|
|
|
|
|
* simulator landings). Pro-gated dynamic import — the never-taken branch
|
|
|
|
|
* keeps the OSS build from ever referencing files it does not have, the
|
|
|
|
|
* same pattern main.tsx uses for mountPro. The prerender script awaits
|
|
|
|
|
* this before asking for routes.
|
|
|
|
|
*/
|
|
|
|
|
export async function loadRouteComponents(): Promise<Record<string, React.FC>> {
|
|
|
|
|
if (import.meta.env.VITE_PRO_BUILD) {
|
|
|
|
|
const m = await import('@pro/pages/marketing');
|
|
|
|
|
routeComponents = { ...OSS_ROUTE_COMPONENTS, ...m.MARKETING_ROUTE_COMPONENTS };
|
2026-08-18 23:35:01 +07:00
|
|
|
// The overlay's own namespace ("pro": landing sections, pricing...),
|
|
|
|
|
// else its t() calls render as keys too.
|
|
|
|
|
try {
|
|
|
|
|
const reg = await import('@pro/i18n/register');
|
|
|
|
|
reg.registerProI18n?.();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.warn(' ⚠ pro i18n not registered for SSR:', (err as Error).message);
|
|
|
|
|
}
|
refactor(routes): OSS is the editor; marketing moves to the overlay
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.
2026-07-30 22:50:14 +07:00
|
|
|
}
|
|
|
|
|
return routeComponents;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 10:08:46 +07:00
|
|
|
/**
|
|
|
|
|
* Returns all routes that have both seoMeta and a renderable component.
|
|
|
|
|
*/
|
|
|
|
|
export function getPrerenderedRoutes() {
|
refactor(routes): OSS is the editor; marketing moves to the overlay
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.
2026-07-30 22:50:14 +07:00
|
|
|
return SEO_ROUTES.filter((r) => r.seoMeta && routeComponents[r.path]);
|
2026-03-25 10:08:46 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render a route's page component to an HTML string.
|
|
|
|
|
*/
|
|
|
|
|
export function render(path: string): string {
|
refactor(routes): OSS is the editor; marketing moves to the overlay
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.
2026-07-30 22:50:14 +07:00
|
|
|
const Component = routeComponents[path];
|
2026-03-25 10:08:46 +07:00
|
|
|
if (!Component) return '';
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return renderToString(
|
|
|
|
|
<MemoryRouter initialEntries={[path]}>
|
|
|
|
|
<Component />
|
2026-04-22 02:45:45 +07:00
|
|
|
</MemoryRouter>,
|
2026-03-25 10:08:46 +07:00
|
|
|
);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.warn(` ⚠ SSR render failed for ${path}:`, (err as Error).message);
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-14 01:22:08 +07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns all example routes to prerender, one per example project.
|
|
|
|
|
*/
|
|
|
|
|
export function getPrerenderedExampleRoutes() {
|
|
|
|
|
return exampleProjects.map((e) => ({
|
|
|
|
|
path: `/examples/${e.id}`,
|
|
|
|
|
title: `${e.title} — Free Arduino Simulator Example | Velxio`,
|
|
|
|
|
description: `${e.description}. Run this example free in your browser — no install, no account required.`,
|
|
|
|
|
url: `https://velxio.dev/examples/${e.id}`,
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render an example detail page to an HTML string.
|
|
|
|
|
*/
|
|
|
|
|
export function renderExample(exampleId: string): string {
|
|
|
|
|
try {
|
2026-08-18 23:35:01 +07:00
|
|
|
// Mounted under its real route so useParams() sees exampleId; rendered
|
|
|
|
|
// bare it had no params and every example page SSR'd as "not found".
|
2026-04-14 01:22:08 +07:00
|
|
|
return renderToString(
|
|
|
|
|
<MemoryRouter initialEntries={[`/examples/${exampleId}`]}>
|
2026-08-18 23:35:01 +07:00
|
|
|
<Routes>
|
|
|
|
|
<Route path="/examples/:exampleId" element={<ExampleDetailPage />} />
|
|
|
|
|
</Routes>
|
2026-04-22 02:45:45 +07:00
|
|
|
</MemoryRouter>,
|
2026-04-14 01:22:08 +07:00
|
|
|
);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.warn(` ⚠ SSR render failed for /examples/${exampleId}:`, (err as Error).message);
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
}
|