SSR with Vike

The Website uses Vike (with vike-react) as its SSR meta-framework. Vike provides file-based routing and the render hooks; the Cloudflare Worker drives them through renderPage().

Global configuration

pages/+config.ts sets the global Vike behavior:

export default {
  title: "Swapps",
  extends: vikeReact,
  ssr: true,                  // SSR for all pages
  hydrationCanBeAborted: true,
  clientRouting: false,       // plain HTML navigation, no SPA client router
  trailingSlash: true,        // URLs always end with "/"
  passToClient: [
    "urlOriginal", "urlPathname", "routeParams",
    "data", "locale", "basePath", "_urlRewritten",
  ],
};

Key choices:

  • ssr: true — every page is rendered on the server.
  • clientRouting: false — navigation uses real HTML links, not a client-side router. Each navigation is a fresh server render.
  • trailingSlash: true — canonical URLs end with / (the Worker also issues trailing-slash redirects).
  • passToClient — these fields are serialized into the client so hydration and components (header config, locale) have what they need.

File-based routing

Routes are defined by the filesystem under pages/. A page directory contains:

File Purpose
+Page.tsx The page React component
+data.ts Server-side data fetching (runs on the server)
+Head.tsx Page-specific <head> tags (optional)
+Layout.tsx Page-specific layout override (optional)
+route.ts Custom route matching (optional)

Dynamic segments use the @param convention, e.g. pages/blog/@slug/+Page.tsx matches /blog/<slug>/. See Project structure for the full page tree.

Rendering pipeline

Server: +onRenderHtml.tsx

onRenderHtml runs on the Worker and produces the full HTML document:

  1. Renders the React tree (<Layout><Page/></Layout>) with renderToString.
  2. Detects the language (URL /es/ prefix or page data) and resolves SEO metadata — either Yoast yoast_head_json from the CMS or a default meta block, with title / description / keywords / robots / canonical, Open Graph, and Twitter cards.
  3. Adds hreflang alternate links (from page-provided alternateUrls for blog posts, or derived from routes.json for static pages).
  4. Injects JSON-LD, GTM and gtag.js scripts (suppressed for Percy CI runs), and font preloads.
  5. Inlines critical CSS (reset, container, header, nav) into a <style> block to avoid FOUC, then mounts the rendered app into <div id="root">.

It returns the document plus the HTTP status code (404 when is404).

Client: +onRenderClient.tsx

onRenderClient hydrates the server HTML in the browser with hydrateRoot(container, <StrictMode><Layout><Page/></Layout></StrictMode>), recovering gracefully from hydration mismatches via onRecoverableError.

Data fetching

Each route's +data.ts exports a data() function that runs on the server during SSR. It receives the Vike pageContext (including the Cloudflare bindings) and fetches what the page needs via the shared fetchData() helper — which uses the API_WORKER Service Binding. The result is exposed to the page component and, because data is in passToClient, to the client.

See Service bindings and Blog system.

Internationalized routing

The Website serves English (default) and Spanish (/es/) from a single set of page files. The global hook pages/+onBeforeRoute.ts runs before Vike picks a page:

  1. It detects the locale from the URL (/es/ prefix → es).
  2. For Spanish URLs, it rewrites the path to its canonical English route using src/locales/routes.json (static routes map + dynamicPatterns regexes), so Vike finds the right +Page.tsx. Dedicated Spanish-only pages (dedicatedEsPages) are left untouched.
  3. It injects locale and basePath into the page context (passed to the client).
{
  "routes": {
    "/services/": { "en": "/services/", "es": "/es/servicios/" }
  },
  "dynamicPatterns": [{ "es": "^/es/blog/([^/]+)/?$", "en": "/blog/$1/" }]
}

This is why adding a page also requires adding its route mapping to routes.json in both languages.

Server entry and the Worker

vike build emits a server entry that scripts/build-worker.ts copies alongside the esbuild-bundled Worker (scripts/patch-server-entry.ts patches the generated entry first). At runtime src/worker.ts imports Vike's renderPage and invokes it per request. See Cloudflare Workers and Build and styling.

Next steps