sveltekit-routing-load-review
Statically review SvelteKit route files (+page.js, +page.server.js, +layout.js, +layout.server.js, +server.ts) to verify universal-vs-server load placement, catching server-only secrets, database clients, or privileged API access that would leak into or execute inside the browser
Install
npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/sveltekit-routing-load-review
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart
git clone https://github.com/VincentChuWaiChow/vanguard-frontier-agentic.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vincentchuwaichow/vanguard-frontier-agentic collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
SvelteKit Routing & Load Function Review
Purpose
Review SvelteKit route files for correct universal-vs-server load function placement without re-litigating progressive-enhancement UX, form-action design, or component styling in every response. This skill exists because SvelteKit's universal load functions (+page.js / +layout.js) run on the server for the initial SSR render and again in the browser on every subsequent client-side navigation — a fact that is routinely missed and turns a misplaced database call or secret reference into a client-side credential exposure, not just a style defect.
When to use
Use this skill when the user asks to:
- review a new or changed SvelteKit route's
loadfunctions before merge, - investigate a secret, database error, or unexpected network call surfacing in the browser console or network tab for a SvelteKit route,
- determine whether data-fetching for a given route is placed in the correct file (
+page.jsvs+page.server.js,+layout.jsvs+layout.server.js), - audit
+layout/+page/+serverfile precedence for a route tree.
Do not use this skill for:
- progressive-enhancement or
<form>action /use:enhanceUX review — usesveltekit-progressive-enhancement-reviewinstead, - pure component-internal state with no
loadfunction involved, - live performance profiling of load waterfalls — that needs runtime tracing, not static review.
Context7 Documentation Protocol
- Resolve the library ID with
resolve-library-id(matched result:/sveltejs/kit) before citing any SvelteKit-specific claim. - Before asserting the universal-vs-server execution model, call
query-docsagainst/sveltejs/kitfor "load functions" and quote the precise rule: a universalload(+page.js/+layout.js) runs on the server during SSR and hydration, then runs again in the browser on every subsequent client-side navigation (or always in the browser if SSR is disabled / the route is a SPA); a serverload(+page.server.js/+layout.server.js) always runs only on the server. If both exist for a route, the serverloadruns first and its return value becomes thedataproperty passed into the universalload. - Verify the SvelteKit major version installed in the repo (
package.json) before asserting version-specific behavior of$env/static/private/$env/dynamic/privateenforcement or server-only module protection — this has evolved across releases (private$env/*client-side imports were blocked starting in a 1.0-pre release). - If Context7 is unavailable, fall back to the
official_docsURLs in this skill'smetadata.jsonand label the claimdocumentation-based, unverified against current release. - Never assume a repo's SvelteKit config (
kit.env.privatePrefix, adapter,ssr/csrpage options) matches defaults without readingsvelte.config.jsand any per-route page-option exports — page options change when/whether a universalloadever runs on the server at all.
Lean operating rules
- First classify every load-bearing route file as universal (
+page.js,+layout.js) or server (+page.server.js,+layout.server.js) by filename suffix alone — never by guessing from content or route name. - Treat every universal
loadfunction as browser-reachable code, full stop. It is not "mostly server-side" or "server-side on first load" — SvelteKit re-runs it client-side on every subsequent navigation by default. - Do not assume SvelteKit's build-time
$env/static/private/$env/dynamic/privateimport guard catches every leak. It blocks direct imports of those modules from client-reachable files, but it does not catch manualprocess.envreads, a$lib/server-protected module re-exporting a secret through a non-.servermodule, or a secret being copied into the plain object a serverloadreturns. - Trace secrets and DB clients through the full import graph, including transitive
$libimports — a universalloadthat imports an innocuous-looking$lib/utils.jswhich itself imports$lib/server/db.jsis still a leak path (and SvelteKit's server-only module protection should catch that specific case at build time; verify it does, do not assume). - Treat any private value present in the plain object a server
load(or+layout.server.js) returns as a candidate leak the moment a universalload, a client component, orpage.data/$page.datacan read it — serverloadoutput crosses the server/client boundary via devalue serialization, it is not automatically safe just because it originated server-side. - Never mark a
+page.server.jsor+layout.server.jsfile itself as a browser-exposure risk for containing secrets or DB calls — those files are always server-only by SvelteKit's routing contract; the risk is in what they choose to return, not that they execute. - Do not flag deep or repeated
+layout.server.jslogic as a leak; flag it as a MEDIUM duplication/maintainability finding only, distinct from HIGH-severity leak findings. - Never execute, build, or run application code as part of this review; this is a static-review skill (Read/Grep/Glob only).
References
Load these only when needed:
- Review workflow and findings contract — use for the step-by-step review procedure, the leak-tracing decision tree, and the required output shape.
- Routing conventions — load only when
+layout/+page/+serverfile precedence or route-tree structure (not just load placement) is in question.
Response minimum
Return, at minimum:
- the route(s) and file(s) in scope, each labeled universal or server load,
- ranked findings with file:line evidence and the full import/data-flow trace for any leak finding,
- evidence level per finding (
repo evidence,documentation-based, orinference), - verdict (approve / approve-with-notes / block),
- open questions or scope the review could not cover (e.g., unverified
svelte.config.jsenv prefix, unread transitive$libmodule).
Files (vanguard-frontier-agentic)
-
references
-
routing-conventions.md 4.4 KB
# Routing Conventions: File Roles and Precedence Use this reference only when a review also needs to assess `+layout`/`+page`/`+server` file precedence or route-tree structure — not for a load-placement-only review (see `workflow-and-output.md` for that). ## Officially grounded file roles Per SvelteKit's routing documentation: - **`+page.svelte`** — the component rendered for a route. - **`+page.js`** — exports a universal `load` (typed `PageLoad`). Runs on the server during SSR/hydration, then again in the browser on subsequent client-side navigations (subject to page options). - **`+page.server.js`** — exports a server `load` (typed `PageServerLoad`), always server-only. Renaming `+page.js` to `+page.server.js` is the documented mechanism for moving a load function that needs a database or private env var out of universal-execution scope. Can also export form `actions`. - **`+layout.svelte`** — wraps child routes; layouts nest by directory structure. - **`+layout.js` / `+layout.server.js`** — same universal/server split as page equivalents, but the returned data is available to the layout's own component and to every child route via `await parent()`. - **`+server.js`/`+server.ts`** — exports HTTP method handlers (`GET`, `POST`, etc.) for a route, making it an API endpoint rather than a page. Always server-only. ## Precedence and data flow - If a route has both a server `load` and a universal `load` (e.g., `+page.server.js` and `+page.js` in the same directory), **the server `load` runs first**. Its return value becomes the `data` property on the `LoadEvent` passed into the universal `load`. Do not describe these as running "in parallel" or "independently" — they are sequential and dependent. - A server `load`'s return value must be serializable via devalue (JSON-representable types plus `BigInt`, `Date`, `Map`, `Set`, `RegExp`, and repeated/cyclical references). A universal `load` has no such restriction and may return non-serializable values (component constructors, class instances, functions) — this asymmetry is a useful signal during review: if a "server load" is returning something devalue cannot serialize, either the classification is wrong or the code will fail at runtime, not just leak. - A `+layout.server.js`'s data is available to every nested route's `load` via the `parent()` function — trace `parent()` calls when checking whether a leak in a layout's server `load` return value actually reaches a specific deeply nested universal `load`. - Prerendered routes invoke `load` at build time, not per-request — flag this distinction only if the review scope includes prerendering/page-option correctness; it does not change the leak-tracing rules above. ## Review-relevant precedence pitfalls - Do not assume a `+page.server.js` with no corresponding `+page.js` implies the route has no client-reachable data flow — the server `load`'s return value still reaches the client as `data`/`$page.data` for the page component itself, even with no universal `load` present. Apply the same leak-tracing rules from `workflow-and-output.md` regardless of whether a sibling universal `load` exists. - Do not assume `+layout.js`/`+layout.server.js` data is scoped narrowly — it is available to the entire subtree beneath that layout, which widens the blast radius of any leak found there compared to a single `+page.js`. - A `+server.ts` in the same directory as a `+page.js`/`+page.server.js` is a separate concern from page load functions; do not conflate an API endpoint's own request-handling logic with the page's load-function review unless the page's universal `load` calls that endpoint via `fetch()`, in which case trace the endpoint's response shape for any leaked field the same way you would trace a server `load`'s return value. ## When to push back Push back if the user asks you to: - "just put it in `+page.js` so it fetches faster" when the data source requires a private credential or database access — that is not faster, it is a credential-exposure defect. - treat a `+layout.server.js` leak as low-severity because "it's just the layout" — layout-scoped leaks reach a wider client-visible subtree than a single page's leak, not a narrower one. - skip verifying the installed SvelteKit version's `$env`/server-only-module enforcement because "the docs say it's blocked" — documentation describes the current release's behavior; it does not prove the repo's pinned version enforces it the same way. -
workflow-and-output.md 7.5 KB
# Review Workflow and Findings Contract Use this reference for the step-by-step review procedure, the leak-tracing decision tree, and the required output shape for a SvelteKit routing/load review. > Version note: `$env/*` client-import enforcement and server-only module protection are Vite-plugin-driven and have evolved across SvelteKit releases. Verify the installed `@sveltejs/kit` version in `package.json` before asserting exact enforcement behavior; do not assume the latest documented behavior applies to an older installed version. ## What people get wrong The naive assumption is: > "`+page.js` is the client file and `+page.server.js` is the server file — as long as I don't put a DB call directly in `+page.js`, I'm safe." That is incomplete in two ways: 1. `+page.js` (and `+layout.js`) are **universal** — SvelteKit runs them on the server for the first SSR render, then re-runs them **in the browser** for every subsequent client-side navigation. It is not "the client file"; it is "the file that runs in both places," and the browser-execution half is where secrets leak. 2. A secret can leak without ever being imported into a universal `load` file directly — it leaks the moment it appears anywhere inside the plain object a server `load` *returns*, because that object is serialized (via devalue) and shipped to the browser as `data`/`$page.data` regardless of whether anything downstream "needed" it. ## Step-by-step workflow 1. **Inventory route files.** For every route directory in scope, list `+page.js`, `+page.server.js`, `+layout.js`, `+layout.server.js`, and `+server.ts`/`+server.js` present. Classify each by filename suffix alone: `.server.` → server load, otherwise → universal load. Do not infer classification from file content or route name. 2. **Confirm page options.** Read any `export const ssr` / `export const csr` in `+page.js`/`+layout.js` for the route. If `ssr = false`, the "universal load runs server-first" assumption is void — the universal load runs client-only, in a SPA-style mount, from the start. If `csr = false`, the universal load never re-runs client-side on navigation (full-page reloads instead) — note this because it changes (lowers) the leak surface but should still be verified rather than assumed. 3. **Trace each universal load's imports.** For every `+page.js`/`+layout.js` in scope, follow its import graph, including transitive `$lib` imports, looking for: - direct imports from `$env/static/private` or `$env/dynamic/private` (SvelteKit's Vite plugin should block this at build time for client-reachable files — verify the repo's SvelteKit version actually enforces this, do not assume), - imports of a module under `$lib/server/` or named `*.server.js`/`*.server.ts` (SvelteKit's server-only module protection should block this too — same verification caveat), - manual `process.env.<SECRET>` reads that bypass both of the above guards, - a database client (Prisma, Drizzle, raw driver, etc.) instantiated or imported directly. 4. **Trace server-`load` return values.** For every `+page.server.js`/`+layout.server.js` in scope, inspect the object literal(s) returned by `load`. Flag any field that is a raw secret, API key, session token, or full DB-record dump not intended for client consumption — that object becomes `data` in the child universal `load` and `$page.data` in every component, i.e. it is client-visible the instant it is returned, independent of whether the universal `load` "does" anything with it. 5. **Check route-tree precedence** only if `+layout`/`+page`/`+server` structural correctness is also in question — load `references/routing-conventions.md` for that sub-review. 6. **Rank and report findings** per the output shape below. ## Leak-tracing decision tree - Universal `load` (or a module it transitively imports) directly imports `$env/static/private`, `$env/dynamic/private`, or reads `process.env.<SECRET>` → **HIGH**. State whether the repo's SvelteKit version's build-time guard would catch this import path or whether it is a bypass (e.g., `process.env` read, or a non-`.server`-suffixed module outside `$lib/server/` that re-exports a value originally sourced from a private env var). - Universal `load` imports a `$lib/server/*` or `*.server.js` module directly → **HIGH** unless the repo's verified SvelteKit version enforces server-only module protection for that exact import path, in which case this is a build-time-blocked case; still flag it as a MEDIUM code-smell (the import should never have been attempted) rather than close the finding silently. - Server `load` (`+page.server.js`/`+layout.server.js`) returns an object containing a raw secret, token, or credential intended only for server-to-service calls → **HIGH**, regardless of whether any universal `load` or component currently reads that field. The leak is the serialization to the client, not the eventual consumption. - Server `load` output is consumed unchanged by a child universal `load` that then spreads or forwards it further (e.g., into a client-side third-party SDK init call) → **HIGH**, trace and cite both hops. - `+page.server.js`/`+layout.server.js` contains a DB call or secret access with no client-visible leak in its return value → **not a finding for this skill**; this is expected, correct placement. - Duplicate near-identical server `load` logic repeated across sibling `+page.server.js` files that could be consolidated into a shared `+layout.server.js` → **MEDIUM**, maintainability/duplication, not a security finding. - `+server.ts` (API route) referenced by a universal `load`'s `fetch()` call, where the `+server.ts` handler itself correctly guards secrets server-side → **not a finding**; note it as an alternative-pattern observation only if the review scope includes route-tree precedence. ## Adversarial checklist Before closing a review with no HIGH findings, confirm: - Did you classify every file by filename suffix, not by assumption? - For every universal `load`, did you check `ssr`/`csr` page options before asserting when/whether it runs server-side vs. client-side? - Did you follow *transitive* `$lib` imports, not just the direct imports in the load file itself? - Did you inspect the actual object returned by every server `load`, not just whether it "looks like" it fetches sensitive data? - Did you verify the installed SvelteKit version's enforcement behavior for `$env/*/private` and server-only modules rather than assuming current docs apply unconditionally? - If you found zero leaks, is that because none exist, or because you didn't trace far enough? ## Output shape Every review response must include: 1. **Scope** — routes and files reviewed, each labeled universal-load or server-load (or both, if the route has paired files). 2. **Findings** — ranked HIGH → MEDIUM → LOW, each with `file:line`, the classification (misplacement vs. leak vs. duplication), the full import/data-flow trace for any leak finding, and a concrete fix sketch (e.g., "move to `+page.server.js`" or "strip `apiKey` field from the returned object before it reaches `data`"). 3. **Evidence level** per finding: `repo evidence` (read the actual file/import), `documentation-based` (asserting SvelteKit's enforcement behavior from docs without confirming the installed version), or `inference` (plausible but unverified, e.g., a dynamic import you could not statically resolve). 4. **Verdict** — approve / approve-with-notes / block. A single HIGH leak finding is a block. 5. **Open questions** — anything the review could not verify (unread `svelte.config.js`, unresolved dynamic import, unconfirmed SvelteKit version).
-
-
metadata.json 1.1 KB
{ "id": "sveltekit-routing-load-review", "name": "SvelteKit Routing & Load Function Review", "type": "skill", "provider": "frontend", "harnesses": [ "claude-code", "cursor", "codex", "gemini", "kiro", "other" ], "summary": "Reviews SvelteKit route files for correct universal-vs-server load placement, preventing server-only logic from executing in the browser.", "source_type": "original", "official_docs": [ "https://kit.svelte.dev/docs/load", "https://kit.svelte.dev/docs/routing", "https://svelte.dev/docs/kit/load", "https://svelte.dev/docs/kit/routing" ], "security_notes": "Server-only secrets, database clients, or privileged third-party API keys reachable from a +page.js/+layout.js (universal load, which runs in the browser too) is a credential-exposure defect — escalate as HIGH, not a style preference. Static-review-only skill: it reads and greps route source but never executes, builds, or runs application code.", "last_verified": "2026-07-02", "path": "skills/frontend/sveltekit-routing-load-review", "author": "github: VincentChuWaiChow", "version": "0.1.0" } -
SKILL.md 6.4 KB
--- name: sveltekit-routing-load-review description: Statically review SvelteKit route files (+page.js, +page.server.js, +layout.js, +layout.server.js, +server.ts) to verify universal-vs-server load placement, catching server-only secrets, database clients, or privileged API access that would leak into or execute inside the browser. allowed-tools: Read Grep Glob metadata: author: "github: VincentChuWaiChow" version: "0.1.0" updated: "2026-07-02" category: architecture --- # SvelteKit Routing & Load Function Review ## Purpose Review SvelteKit route files for correct universal-vs-server `load` function placement without re-litigating progressive-enhancement UX, form-action design, or component styling in every response. This skill exists because SvelteKit's universal `load` functions (`+page.js` / `+layout.js`) run on the server for the initial SSR render **and again in the browser** on every subsequent client-side navigation — a fact that is routinely missed and turns a misplaced database call or secret reference into a client-side credential exposure, not just a style defect. ## When to use Use this skill when the user asks to: - review a new or changed SvelteKit route's `load` functions before merge, - investigate a secret, database error, or unexpected network call surfacing in the browser console or network tab for a SvelteKit route, - determine whether data-fetching for a given route is placed in the correct file (`+page.js` vs `+page.server.js`, `+layout.js` vs `+layout.server.js`), - audit `+layout`/`+page`/`+server` file precedence for a route tree. Do not use this skill for: - progressive-enhancement or `<form>` action / `use:enhance` UX review — use `sveltekit-progressive-enhancement-review` instead, - pure component-internal state with no `load` function involved, - live performance profiling of load waterfalls — that needs runtime tracing, not static review. ## Context7 Documentation Protocol - Resolve the library ID with `resolve-library-id` (matched result: `/sveltejs/kit`) before citing any SvelteKit-specific claim. - Before asserting the universal-vs-server execution model, call `query-docs` against `/sveltejs/kit` for "load functions" and quote the precise rule: a universal `load` (`+page.js`/`+layout.js`) runs on the server during SSR and hydration, then runs **again in the browser** on every subsequent client-side navigation (or always in the browser if SSR is disabled / the route is a SPA); a server `load` (`+page.server.js`/`+layout.server.js`) always runs only on the server. If both exist for a route, the server `load` runs first and its return value becomes the `data` property passed into the universal `load`. - Verify the SvelteKit major version installed in the repo (`package.json`) before asserting version-specific behavior of `$env/static/private` / `$env/dynamic/private` enforcement or server-only module protection — this has evolved across releases (private `$env/*` client-side imports were blocked starting in a 1.0-pre release). - If Context7 is unavailable, fall back to the `official_docs` URLs in this skill's `metadata.json` and label the claim `documentation-based, unverified against current release`. - Never assume a repo's SvelteKit config (`kit.env.privatePrefix`, adapter, `ssr`/`csr` page options) matches defaults without reading `svelte.config.js` and any per-route page-option exports — page options change when/whether a universal `load` ever runs on the server at all. ## Lean operating rules - First classify every load-bearing route file as universal (`+page.js`, `+layout.js`) or server (`+page.server.js`, `+layout.server.js`) by filename suffix alone — never by guessing from content or route name. - Treat every universal `load` function as browser-reachable code, full stop. It is not "mostly server-side" or "server-side on first load" — SvelteKit re-runs it client-side on every subsequent navigation by default. - Do not assume SvelteKit's build-time `$env/static/private` / `$env/dynamic/private` import guard catches every leak. It blocks direct imports of those modules from client-reachable files, but it does not catch manual `process.env` reads, a `$lib/server`-protected module re-exporting a secret through a non-`.server` module, or a secret being copied into the plain object a server `load` returns. - Trace secrets and DB clients through the full import graph, including transitive `$lib` imports — a universal `load` that imports an innocuous-looking `$lib/utils.js` which itself imports `$lib/server/db.js` is still a leak path (and SvelteKit's server-only module protection should catch that specific case at build time; verify it does, do not assume). - Treat any private value present in the plain object a server `load` (or `+layout.server.js`) returns as a candidate leak the moment a universal `load`, a client component, or `page.data`/`$page.data` can read it — server `load` output crosses the server/client boundary via devalue serialization, it is not automatically safe just because it originated server-side. - Never mark a `+page.server.js` or `+layout.server.js` file itself as a browser-exposure risk for containing secrets or DB calls — those files are always server-only by SvelteKit's routing contract; the risk is in what they choose to *return*, not that they execute. - Do not flag deep or repeated `+layout.server.js` logic as a leak; flag it as a MEDIUM duplication/maintainability finding only, distinct from HIGH-severity leak findings. - Never execute, build, or run application code as part of this review; this is a static-review skill (Read/Grep/Glob only). ## References Load these only when needed: - [Review workflow and findings contract](references/workflow-and-output.md) — use for the step-by-step review procedure, the leak-tracing decision tree, and the required output shape. - [Routing conventions](references/routing-conventions.md) — load only when `+layout`/`+page`/`+server` file precedence or route-tree structure (not just load placement) is in question. ## Response minimum Return, at minimum: - the route(s) and file(s) in scope, each labeled universal or server load, - ranked findings with file:line evidence and the full import/data-flow trace for any leak finding, - evidence level per finding (`repo evidence`, `documentation-based`, or `inference`), - verdict (approve / approve-with-notes / block), - open questions or scope the review could not cover (e.g., unverified `svelte.config.js` env prefix, unread transitive `$lib` module).
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.