How apps/web turns generated content into prerendered SvelteKit pages, and where to change routes, SEO and runtime integrations.
| Reference |
Value |
| Subject |
Routes, content catalog, rendering and caching, SEO surfaces, locales, runtime integrations; for web contributors |
| Verified source |
apps/web on main at 950ba889, checked 2026-11-07; media resolution, project share copy, image blocks, section defaults, the analytics relay and Blog redirects re-verified on main at 0461e045, checked 2026-11-10 |
| Support state |
Application contracts checked against main 950ba889; main 0461e045 (#692) pins design v0.40.0. Public requests on November 7, 2026 returned the 950ba889 revision's project redirect and robots tag |
Stack
SvelteKit 3 with Svelte 5 runes, TypeScript, Tailwind CSS 4, Vite and @sveltejs/adapter-vercel pinned to the nodejs22.x runtime. GSAP and Embla drive motion and carousels. Configuration lives in vite.config.ts. Its ssr.noExternal list bundles packages whose module format breaks Vercel's Node runtime, including sanitize-html and its ESM-only parser chain (htmlparser2, domhandler, domutils, domelementtype, dom-serializer, entities).
flowchart LR
G["src/lib/content<br/>21 generated modules"] --> A["catalog<br/>availability rules"]
A --> L["+layout.server.ts<br/>+page.server.ts loaders"]
G --> D["Direct reads<br/>chrome, CTAs, fallbacks"]
L --> P["Prerendered pages<br/>EN, FR, ES"]
D --> P
P --> B["Browser"]
Route map
| Route |
Source |
Notes |
| Home, about, contact, projects, services, blog, blog/personal |
src/routes/[[lang=locale]]/ |
Optional locale segment: EN has no prefix, /fr and /es match through src/params.ts |
projects/[slug], blog/[slug], services/[id], legal/[slug] |
Same tree |
Detail pages for CMS rows; [email protected] resets the layout for article pages |
/sitemap.xml, /robots.txt |
src/routes/ |
Functions; host-aware |
/og/[type=ogType]/[slug].png |
src/routes/og/ |
Prerendered Blog and Project cards |
/llms.txt, /llms-full.txt |
src/routes/, src/lib/server/llms.ts |
Prerendered crawler text from generated content |
/api/weather |
src/routes/api/weather/ |
Function with a 30 minute shared cache |
/relay/e |
src/routes/relay/e/+server.ts |
POST function, never prerendered: forwards each Plausible event once with the visitor's address; 404 when PUBLIC_PLAUSIBLE_DOMAIN is empty |
/work → /projects (English path only, src/routes/work/). In every locale: /tech-stack → /services; /services/internal-tooling → /services/data-pipeline; /services/sql-development → /services/database-engineering; /projects/yesid-dev-design → /projects/yesid-design |
+server.ts redirects |
Permanent redirects kept on functions so CDN cache headers survive |
blog/one-brain-for-five-ai-tools-t3-code-and-notion and its FR and ES former slugs → the requested locale's "four AI tools" post |
blog/<old-slug>/+server.ts, server/four-ai-tools-redirect.ts |
Permanent; every old spelling follows the URL's locale (#676) |
Content catalog and SEO
Routes read the synchronous catalog from src/lib/catalog/index.ts. It reads generated modules; it never fetches editorial content at request time.
| Interface |
Contract |
projects |
bySlug, locale-aware available, featured, byService; listings project away case-study bodies |
services |
byId, available, adjacent |
blog |
Available language rows, slug/body/category and the post's topic icon: one of 21 SVGs in src/lib/assets/blog-icons/, read raw by name. The exporter sets the name from the post's Directus svg_illustration, or a stable post-id hash over four topics per category when none is set. catalog/icon-assets.test.ts checks the library and every fixture post's icon |
meta, nav, legal, content |
Site defaults, route overrides, navigation, legal copy and page blocks |
catalog/availability.ts |
One site-page, entity-status and locale policy for routes, prerender, sitemap, OG and LLM text |
adapters/page-seo.ts |
resolvePageSeo composes catalog data through static defaults or route factories; validates PageSeo |
createContentCatalog(source) injects rows and locale/page policy for tests. Layout chrome and client fallbacks may read generated modules directly.
Rendering, gating and caching
| Concern |
Contract |
Source |
| Prerender |
Root layout sets prerender = true; every page prerenders with explicit entries per published locale and slug. Endpoints opt out individually |
src/routes/+layout.server.ts, src/lib/server/prerender-entries.ts |
| Page registry |
A page renders only if site_pages publishes its path or its listing prefix; otherwise 404. The sitemap uses the same predicate |
src/lib/catalog/availability.ts |
| Layout fallbacks |
Layouts resolve catalog defaults and route SEO; errors use status-specific or generic generated copy |
+layout.server.ts, +layout.ts |
| Cache headers |
HTML and __data.json get s-maxage=86400, stale-while-revalidate=604800 on the CDN and max-age=0 in the browser, set only in hooks.server.ts |
hooks.server.ts |
| Theme and fonts |
@yesid/ui/head injects the theme boot script before paint; Link preload headers carry the self-hosted fonts |
Same |
| Errors |
handleError logs non-404 failures to the function log and resolves localized CMS error copy |
Same |
| Media |
The generated media-assets module maps each referenced file UUID to a committed mirror under static/images or a file the live export wrote to static/images/cms/<file-uuid>/ (Git-ignored); fixture builds map unmirrored UUIDs to a labelled placeholder. An ID missing from the map falls back to Directus only in Vite dev and fails in production |
src/lib/directus/assets.ts, apps/cms/scripts/lib/build-media.ts |
| Metric display |
Home Service cards, Service listings/details and Project impact cards show values and labels. Optional CMS source/date fields are not displayed or included in metric text in llms-full.txt |
HomeServices.svelte, ServiceCard.svelte, ServiceDetailPage.svelte, ProjectImpactCard.svelte, server/llms.ts |
SEO and locales
compose-page-seo.ts and route-seo-factories.ts merge site metadata, SEO defaults and per-route route_seo rows into one PageSeo, so canonical, OG, JSON-LD and sitemap agree. The <html lang> value comes from the URL path. Published locales are EN, FR and ES. Entity availability also checks its site page and entity status; Blog uses separate language rows. Missing optional FR or ES localized fields fall back to English. Indexable pages carry a max-image-preview:large robots tag (pageRobots in utils/seo-defaults.ts); noindex pages get theirs from SeoHead. Project descriptions come from og/project-copy.ts: the localized one-liner (English fallback) with the call to action, the one-liner alone, then leading complete overview sentences with the call to action when it fits; a result outside 50 to 200 characters uses the site default. Blog descriptions close with their card's call to action when the result stays within 200 characters (withShareCta in og/share-copy.ts).
Runtime integrations
| Integration |
Contract |
Source |
| Contact |
Browser POST to Web3Forms with a 15 second abort; submit enabled only after hydration; drafts persist per tab |
src/lib/components/contact/ContactPage.svelte |
| Analytics |
@yesid/analytics Plausible client, started once from the root layout; host-gated, cookieless, eight allowlisted events, posted to same-origin /relay/e, which forwards them to Plausible; Vercel Web Analytics and Speed Insights start on the same production host. Cloudflare's injected beacon is provider configuration |
src/lib/analytics/, src/routes/relay/e/ |
| Weather |
/api/weather?lang= returns Montreal weather or null (no key, prerender, or provider failure) |
src/lib/utils/weather.ts |
| Transit KPIs |
Home hero polls https://transit.yesid.dev/api/v1/kpis every 45 seconds while visible; LIVE requires complete finite aggregate lanes and a usable route; retain the last real reading through one failure with its age advancing, then DEMO after a second consecutive failure |
src/lib/live/live-kpis.ts |
Code layout
| Need |
Where |
| Page components by area |
src/lib/components/{home,about,blog,projects,services,contact,shared,brand,cms} |
| Motion actions and scroll scrubs |
src/lib/motion/ (policy from @yesid/motion); utils/morphGate.ts lets a running SVG entrance or morph finish before the latest input plays, actions/morphHover.ts is the one path morph and actions/pointerHover.ts the touch-safe hover |
| Share cards |
src/lib/og/: card.ts layout, render.ts resvg rasterizer, share-copy.ts calls to action, project-copy.ts project card line and description, validate-project-lines.ts and glyph-coverage.ts export-time fit and font checks |
| Utilities, locale routing, SEO defaults |
src/lib/utils/ |
| Server-only helpers (Markdown, Shiki, LLM text, redirects, indexing) |
src/lib/server/ |
| Static assets and committed mirrors |
static/ (images, OG cards, the six service SVGs named by CMS services.svg, favicons) |
| Release gate scripts |
scripts/ (check-client-payload.mjs, check-csp.ts, check-ssr-runtime.mjs, check-svelte-warnings.mjs, generate-og-cards.ts) and tools/ (design gates) |
Reading position and link activation