How would you architect a large-scale Svelte/SvelteKit app?
Svelte Developer
answer
A scalable Svelte architecture treats SvelteKit as the composition kernel: routes are thin, business logic lives in domain modules, and UI is a reusable component library. Keep SSR first with smart CSR hydration (islands, loading="lazy", hydrate-client only where it pays). Centralize data with typed load functions and API clients, cache at edge, and stream where possible. Enforce boundaries via workspaces/monorepo, linting, tests, and story-driven components. Measure and budget for RUM, not guesswork.
Long Answer
Designing a large-scale Svelte/SvelteKit application means combining clear module seams, disciplined data flow, and ruthless attention to SSR/CSR performance. The aim is to keep routes thin, domain logic portable, and UI highly reusable—so teams ship quickly without creating a performance or maintenance debt.
1) Architectural shape: domain slices over routes
Model domains (Accounts, Catalog, Checkout, Analytics) in independent packages under a monorepo (pnpm workspaces/Turborepo). Each domain exports:
- services (pure TS, platform-agnostic),
- stores (Svelte stores or Rx-like wrappers),
- components (headless + skinned variants),
- types (shared DTOs and Zod schemas).
Routes in src/routes become thin adapters: they call domain services inside +page.server.ts / +layout.server.ts and pass typed data to Svelte components. This keeps rendering swappable (SSR, SSG, CSR) while business logic remains reusable in tests and workers.
2) Data flow: typed contracts and predictable loading
Use SvelteKit load functions as the boundary. Server-load fetches compose backend calls, validate with Zod, and return minimal view models. Prefer server-side joins to avoid waterfalling in the browser. For client interactivity, hydrate only the parts that need it; pass initial state from SSR to client to avoid duplicate fetches. Centralize HTTP with a small client that injects auth, tracing headers, and handles retries. All data types are generated from OpenAPI/GraphQL or hand-written with strict schemas to stop shape drift.
3) Reuse via headless + presentational components
Build a design system library as a separate package. Each component has:
- headless logic (state machines, accessibility, keyboard handling),
- presentational shell (tokens, themes),
- stories in Storybook for visual specs,
- contract tests to lock behavior.
This separation enables reuse across pages and even across brands (swap themes, keep logic). Compound components (Modal → Dialog → Sheet) share primitives to minimize code duplication.
4) Performance: SSR first, CSR surgically
Default to SSR for first meaningful paint. Defer client hydration on non-interactive regions using SvelteKit’s islands approach: split components, ship small entry points, and hydrate only interactive islands. Use <img loading="lazy">, <video preload="metadata">, and intersection observers. Bundle-split via route-level chunks; extract the design system into a shared chunk to improve cache hits. Apply code-splitting on heavy widgets (charts, editors) with dynamic imports and onMount. For lists, employ windowing and progressive rendering (streamed responses or skeletons). Measure with RUM; cap CLS/LCP budgets in CI.
5) Caching strategy: edge, HTTP, and stores
Push cache decisions to the edge where possible. Mark stable pages SSG/ISR-like using prerender or selective invalidation hooks. For dynamic pages, set strong HTTP caching for images and static chunks, vary by auth/locale where needed, and use ETags for revalidation. In-app, memoize domain queries; store ephemeral UI state in writable stores, but keep server truth in SSR responses to avoid drift.
6) State management: minimal and explicit
Prefer props and derived stores; avoid global mutable state. When cross-route state is necessary (cart, session), isolate in a well-typed store that syncs with cookies or server endpoints. Use actions (Svelte actions) for DOM concerns (outside click, resize). For complex flows, model state machines (e.g., XState-like patterns), keeping transitions pure and testable. This preserves modularity and prevents incidental coupling.
7) Routing and layouts: composition over duplication
Use nested +layout.svelte structures to share chrome and data (e.g., user, feature flags). Co-locate assets/tests with routes and domains. Avoid fat layout loads: fetch just what all children need and pass down via data. For authenticated sections, a guarded layout checks session early, redirecting before any heavy work. This improves SSR performance and keeps UX crisp.
8) Build system and monorepo discipline
Adopt pnpm workspaces with strict version constraints. Use TS project references for incremental builds. Lint with eslint + svelte-eslint, format with Prettier, check types in CI. Turborepo pipelines speed selective builds and tests. For adapters (Node server, Cloudflare, Vercel), keep adapter config per environment but one codebase. Lock dependencies with Renovate schedules to avoid drift.
9) Testing and accessibility at scale
Follow a pyramid:
- unit tests for services/stores,
- component tests for interactive pieces (Playwright component mode),
- contract tests for load functions (shape validation),
- E2E smoke flows per critical journey.
Include a11y checks (axe) in Storybook CI; enforce keyboard traps and aria attributes in headless primitives. This keeps code reuse safe and prevents regressions.
10) Observability and budgets
Instrument logs on the server side with request IDs and timings in load functions. Emit custom measures for data fetch time and hydration duration. Add synthetic and RUM monitors with thresholds (LCP, TBT, INP). Expose a “performance budget” dashboard so teams can see when routes exceed limits, and force PR comment bots to show diff in bundle sizes and LCP deltas.
11) Internationalization and theming
Localize at the edge of the page: resolve locale in +layout.server.ts, load dictionaries per route chunk, and avoid shipping all locales to the client. Theming uses CSS variables with prefers-color-scheme fallbacks. This keeps bundles slim and CSR performance predictable.
12) Progressive enhancement and offline
Design everything to work without JS where feasible (links, forms). Layer interactivity with actions and small stores. If offline is a requirement, add a service worker to cache static assets and critical HTML shells; keep invalidation explicit to avoid stale UX.
The result is a SvelteKit architecture that scales: domains are autonomous, UI is reusable, data is typed and cached, and SSR stays fast while CSR is applied with surgical precision.
Table
Common Mistakes
- Treating routes as feature silos; duplicating logic instead of centralizing in domain services.
- Hydrating entire pages by default; shipping heavy client bundles when SSR alone suffices.
- Global stores for everything, causing hidden coupling and stale state.
- Waterfall client fetches in load rather than server-side composition with validation.
- Ignoring design system discipline; one-off components that resist reuse and theming.
- No performance budgets—optimizing by feel, not by RUM metrics and CI checks.
- Overusing GraphQL/client caching to paper over slow SSR instead of fixing data joins.
- Monorepo without ownership, allowing dependency drift and slow builds.
Sample Answers (Junior / Mid / Senior)
Junior:
“I keep routes thin and move logic to domain services. I use server load for SSR data, validate with Zod, and pass minimal props to components. I only hydrate interactive parts and rely on stores sparingly.”
Mid:
“My SvelteKit architecture organizes code in workspaces: services, stores, and a design system. SSR is default; I split bundles per route and lazy-load charts/editors with onMount. I cache at the edge and use RUM to guard LCP. Testing covers services, components, and a few E2E flows.”
Senior:
“I architect domains as packages with typed contracts and headless components. Data composition lives in server load with schema validation and view models. Hydration is island-based, with dynamic imports and windowed lists. We enforce budgets in CI, run Storybook a11y checks, and track bundle diffs. Observability spans server timings and web vitals; ownership and ADRs keep the monorepo healthy.”
Evaluation Criteria
Look for a coherent Svelte architecture where routes are thin and domains provide reusable services/stores/components. Strong answers favor SSR by default, hydrate only interactive islands, and validate data in typed server load functions. They demonstrate caching strategies (edge/HTTP) and careful state scoping, plus a design system split into headless and themed layers. Build discipline (monorepo, incremental TS, lint/type checks) and testing depth (unit, component, contract, E2E, a11y) are essential. Red flags: global stores everywhere, full-page hydration, ad-hoc components, client waterfalls, and no performance budgets or ownership model.
Preparation Tips
- Spin a pnpm monorepo with domain packages and a design system.
- Implement two routes that SSR data via server load, validated with Zod.
- Convert a heavy widget to an island: dynamic import, onMount, and windowing.
- Add RUM (web vitals) and a bundle-size bot; set fail thresholds for LCP and total JS.
- Build Storybook for headless primitives; run axe in CI and write interaction tests.
- Add edge caching (ETag) and a prerendered marketing page; measure TTFB/INP.
- Document an ADR on “SSR-first, hydrate selectively,” with a checklist for new routes.
- Profile a list page before/after windowing and record the impact in a performance log.
Real-world Context
A travel marketplace moved pricing logic into server load functions with schema validation and shipped island-hydrated filters; LCP dropped 28% and bundle size by 35%. A SaaS dashboard split charts to dynamic imports and added windowing; TBT fell sharply and interactions stayed smooth under load. An e-commerce brand created a headless design system, enabling two storefronts to reuse the same primitives with different skins; build times improved via TS project refs, and defects declined thanks to Storybook a11y tests. These outcomes show that SvelteKit architecture focusing on SSR-first, typed data, and component discipline scales without sacrificing iteration speed.
Key Takeaways
- Keep routes thin; put logic in domain services and typed server loads.
- Default to SSR; hydrate only interactive islands and split heavy widgets.
- Build a headless-first design system to maximize code reuse.
- Cache at edge/HTTP and validate data with schemas to stabilize SSR/CSR performance.
- Enforce budgets, tests, and ownership in a disciplined monorepo.
Practice Exercise
Scenario:
You are building a multi-tenant SvelteKit analytics app with dashboards, filters, and exports. Traffic spikes during monthly reports and marketing launches. The product must remain fast under SSR, hydrate only where needed, and allow teams to iterate independently on domains.
Tasks:
- Propose a monorepo layout (packages for accounts, reports, design-system, shared-types). Describe ownership and publish strategy.
- For the “Team Dashboard” route, design the +layout.server.ts and +page.server.ts data flow: which data is fetched where, what is validated with Zod, and what minimal view model is returned.
- Identify three interactive islands (filters panel, chart area, export modal) and explain how you will hydrate each with dynamic imports and onMount.
- Specify caching: which responses are prerendered, which use ETags or short-lived CDN TTLs, and how tenant and locale vary cache keys.
- Define stores for cross-route state (session, feature flags) and show how they sync with cookies without duplicating server truth.
- Add performance budgets (LCP, TBT, INP) and a CI step that fails on bundle regressions; include RUM wiring and server timing headers.
- Outline tests: unit for services, component tests for the filters and charts, and one E2E for the export flow with a11y assertions.
Deliverable:
An architecture note and skeleton repo plan demonstrating SvelteKit architecture that preserves modularity, maximizes code reuse, and sustains high SSR/CSR performance across pages.

