Sitemap Generation

generateSitemap() builds a sitemap.xml string from your route tree. It's a pure function over tree data — no filesystem access, no server — so you run it wherever fits your deploy: a build script, a scheduled job, or an on-demand route handler.

import { generateSitemap } from '@emkodev/emroute/runtime/sitemap';

const xml = await generateSitemap(routeTree, {
  baseUrl: 'https://example.com',
});

routeTree is the RouteNode tree. There's no getter for it on a running Emroute instance — ComponentContext doesn't carry it either — so read it the same way bootEmrouteApp() does: fetch ROUTES_MANIFEST_PATH off your Runtime and parse it.

Static routes are automatic

Every route with no dynamic segment is included with no extra config — /about, /pricing, directory-index catch-alls, all walked from the tree.

Dynamic routes need an enumerator

A route like /projects/:id has no fixed set of URLs, so it's excluded by default. Provide an enumerators function keyed by the route's pattern (not a URL, and not basePath-prefixed) to list the concrete values:

const xml = await generateSitemap(routeTree, {
  baseUrl: 'https://example.com',
  enumerators: {
    '/projects/:id': async () => {
      const projects = await db.projects.findAll();
      return projects.map((p) => p.id);
    },
  },
});

Key format: enumerators and routes (below) are keyed by the raw route pattern as it appears in the tree — /projects/:id, not /html/projects/:id. The basePath option controls the URL prefix written into <loc>, it does not change how you key these maps. Mixing the two up silently drops the override — no error, the route just falls back to defaults.

Per-route and default metadata

<lastmod>, <changefreq>, <priority> are all optional per the sitemap protocol. Set defaults for everything, then override specific routes:

const xml = await generateSitemap(routeTree, {
  baseUrl: 'https://example.com',
  defaults: { changefreq: 'weekly', priority: 0.5 },
  routes: {
    '/': { priority: 1.0, changefreq: 'daily' },
    '/pricing': { priority: 0.8 },
  },
});

basePath

As of 1.12.7, defaults to /html — the canonical, crawlable SSR view. /app renders the same shell for every route (nothing route-specific for a crawler to index) and bare paths just redirect, so neither belongs in a sitemap. On 1.12.6 and earlier, the default was an empty prefix — pass basePath: '/html' explicitly if you're pinned to an older release:

const xml = await generateSitemap(routeTree, {
  baseUrl: 'https://example.com',
  basePath: '/html', // default — usually omit this
});

Pass '' only if your route tree's patterns already include a path prefix you want reflected as-is.

Serving it: a build-time script, not a route

generateSitemap() returns a string. The route tree only changes when your code does — a new route is a redeploy either way — so treat sitemap.xml as a build artifact, not something computed per-request. This guide's own site does exactly that, in scripts/generate-sitemap.ts:

import { UniversalFsRuntime } from '@emkodev/emroute/runtime/universal/fs';
import { ROUTES_MANIFEST_PATH } from '@emkodev/emroute/runtime';
import { generateSitemap } from '@emkodev/emroute/runtime/sitemap';
import { resolve } from 'node:path';

const SITE_URL = 'https://example.com';
const appRoot = resolve(import.meta.dirname!, '..');
const runtime = new UniversalFsRuntime(appRoot);

const tree = await (await runtime.query(ROUTES_MANIFEST_PATH)).json();
const xml = await generateSitemap(tree, {
  baseUrl: SITE_URL,
  defaults: { changefreq: 'weekly', priority: 0.5 },
  routes: { '/': { priority: 1.0 } },
});

await Deno.writeTextFile(resolve(appRoot, 'sitemap.xml'), xml);

Run it as a task (deno task sitemap / an npm script), commit the output, and let your server serve sitemap.xml as a static file alongside robots.txt. Re-run it whenever you add or remove routes.

Limits

Capped at 50,000 URLs per the sitemap protocol — generateSitemap() stops collecting once it hits the limit rather than producing an invalid file. For larger sites, shard by section (call it once per top-level route prefix) and serve a sitemap index that links to each shard.

Next: Server Setup