web-meta-framework-sveltekit-4d529a — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-meta-framework-sveltekit-4d529a (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: Load functions fetch, form actions mutate,+server.tsserves external clients, and hooks handle what applies to every request. Data reaches a component as thedataprop and an action's result as theformprop, both typed from the generated$types. Two facts change most answers below:+page.server.tsruns only on the server while+page.tsalso runs in the browser, andfail()returns whileerror()andredirect()throw.
Detailed Resources:
parent(), invalidationfail(), use:enhance, redirect orderinghandle, handleFetch, handleError, init, reroute, transport, sequence+server.ts verbs, streaming responses, uploads, content negotiation+page.server.ts versus +page.ts, and it turns onwhether the code may run in the browser; follow examples/load-functions.md.
+server.ts route with one export per HTTP verb;follow examples/api-routes.md.
<critical_requirements>
Put anything touching a database, a secret or a cookie in `+page.server.ts`. A +page.ts load also runs in the browser, so whatever it imports is in the client bundle.
Handle mutations with form actions rather than API routes. An action gets progressive enhancement, CSRF protection and automatic revalidation; a fetch to a +server.ts route gets none of them for free.
Return `fail(status, data)` for a validation failure, and put the values the user typed in it. fail populates the form prop, so the page re-renders with the input intact — error() throws to +error.svelte and the form is gone.
Validate on the server even where the browser already checked. required and type="email" are a courtesy to the user; a POST can arrive without passing through the form at all.
Type load functions and page props from the generated `./$types`. PageServerLoad, PageProps and LayoutProps are derived from the route's own files, so a renamed parameter becomes a compile error.
Add `use:enhance` to every form. Without it a submission is a full page reload, which still works and is much slower.
Call `redirect()` outside any `try`. It signals by throwing, so a surrounding catch swallows the navigation and reports a failure that did not happen.
</critical_requirements>
Auto-detection: +page.svelte, +page.ts, +page.server.ts, +layout.svelte, +layout.ts, +layout.server.ts, +error.svelte, +server.ts, hooks.server.ts, hooks.client.ts, hooks.ts, load function, form actions, use:enhance, handle, handleFetch, handleError, handleValidationError, init, reroute, transport, sequence, $app/navigation, $app/forms, $app/state, $env/static/private, $env/dynamic/public, PageServerLoad, PageLoad, LayoutServerLoad, RequestHandler, PageProps, LayoutProps, fail, redirect, error, invalidate, invalidateAll, depends, event.locals, .remote.ts
Applies to:
+server.tsHandled elsewhere:
is settled here
locals
FormData; which library defines the schema is a separatechoice
<philosophy>
SvelteKit is built on web platform objects rather than framework abstractions: Request, Response, URL, Headers and FormData are the whole vocabulary. That is why a form works without JavaScript, why caching is a Cache-Control header, and why an error is a status code.
The framework's shape is four roles, each with one job:
Request → hooks.server.ts (handle) → +layout.server.ts (load) → +page.server.ts (load) → +page.svelte
← form actions (POST)data and form as propsType safety is generated rather than declared: $types is derived from the file tree, so the types follow a renamed route without anyone updating them.
</philosophy>
<patterns>
Directories under src/routes/ are URL segments; the + files decide what each segment does.
| File | Purpose | Runs on |
|---|---|---|
+page.svelte | The page itself | Server (SSR) + client |
+page.ts | Universal load | Server + client |
+page.server.ts | Server load and form actions | Server only |
+layout.svelte | Wrapper for the segment and below | Server (SSR) + client |
+layout.ts | Universal layout load | Server + client |
+layout.server.ts | Server layout load | Server only |
+error.svelte | Error boundary for the segment | Server (SSR) + client |
+server.ts | HTTP endpoint, one export per verb | Server only |
Bracket depth chooses the segment kind — [slug], [...path], [[lang]] — and (name) groups routes under a shared layout without appearing in the URL.
Full code: examples/core.md
The default choice: it can read the database, the cookies and the private environment, because nothing in it reaches the browser.
// src/routes/blog/+page.server.ts
export const load: PageServerLoad = async ({ url, locals }) => {
if (!locals.user) error(401, "Not authenticated");
const page = Number(url.searchParams.get("page") ?? "1");
const [posts, total] = await Promise.all([listPosts(page), countPosts()]);
return { posts, pagination: { page, total } };
};Full code: examples/load-functions.md
+page.ts runs on the server for the first request and in the browser for every navigation after, so it suits a public API and nothing that needs a secret.
// src/routes/weather/+page.ts
export const load: PageLoad = async ({ fetch, params }) => {
const response = await fetch(
`https://api.example.com/forecast/${params.city}`,
);
if (!response.ok) error(response.status, "Failed to load forecast");
return { forecast: await response.json() };
};That fetch is SvelteKit's own: it inherits cookies, deduplicates, and calls an internal +server.ts route directly on the server rather than over HTTP.
Full code: examples/load-functions.md
Data returned from a layout load is available to every page beneath it, which makes a layout the natural place for an auth check.
// src/routes/dashboard/+layout.server.ts
export const load: LayoutServerLoad = async ({ locals }) => {
if (!locals.user) redirect(303, "/login");
return { user: locals.user, notifications: await unreadFor(locals.user.id) };
};A child reaches it with await parent() — called after its own independent queries have started, since parent() blocks.
Full code: examples/load-functions.md
Actions live beside the load in +page.server.ts. A page has either one default action or any number of named ones.
export const actions: Actions = {
login: async ({ request, cookies }) => {
const data = await request.formData();
const email = data.get("email")?.toString() ?? "";
if (!email) return fail(400, { email, message: "Email is required" });
cookies.set("session", await createSession(email), { path: "/" });
redirect(303, "/dashboard"); // outside any try — it throws
},
};<form method="POST" action="?/login" use:enhance>?/login targets the named action; the returned fail payload arrives as the form prop, which is what refills the inputs.
Full code: examples/form-actions.md
Await what the page cannot render without and return the rest unawaited; {#await} covers the three states in the markup.
export const load: PageServerLoad = async ({ locals }) => {
const user = await getUser(locals.user.id); // blocks the first paint
return {
user,
analytics: getAnalytics(locals.user.id), // streams in later
};
};{#await data.analytics}
<div class="skeleton">Loading…</div>
{:then analytics}
<p>Views: {analytics.views}</p>
{:catch error}
<p role="alert">Failed to load analytics</p>
{/await}Only a server load can stream — the values have to be serialisable.
Full code: examples/load-functions.md
error(status, message) throws to the nearest +error.svelte, which reads page from $app/state.
<!-- src/routes/+error.svelte -->
<script lang="ts">
import { page } from '$app/state';
</script>
<h1>{page.status}</h1>
<p>{page.error?.message ?? 'Something went wrong'}</p>The boundary walks up the tree, so an +error.svelte in a segment keeps the failure inside it.
Full code: examples/core.md
hooks.server.ts runs for every request — the single place to establish who the caller is.
export const handle: Handle = async ({ event, resolve }) => {
const sessionId = event.cookies.get("session");
event.locals.user = sessionId ? await getUserFromSession(sessionId) : null;
return resolve(event);
};event.locals is request-scoped, so every load function and action downstream reads the same user. sequence() composes several hooks; handleFetch, handleError, init, reroute and transport cover the rest.
Full code: examples/hooks.md
Three exports decide how a route is rendered, and they apply to the segment and everything under it.
export const prerender = true; // static HTML at build time
export const ssr = false; // client-only rendering
export const csr = false; // no JavaScript shipped at allPrerender static content, disable SSR for a page that needs browser APIs at first render, and disable CSR for pages with no interactivity.
Full code: reference.md
</patterns>
<red_flags>
Breaks at runtime:
+page.ts — the module is bundled for thebrowser
redirect() inside a try — it throws, so the catch reports a failure instead of navigatingfail(...) called without return — it produces a value rather than throwing, so executioncontinues
method="POST" — a GET reaches the load function, and the action never runs+server.ts routes are public endpointsunless a transport hook encodes it
event.locals accessed without an App.Locals declaration in app.d.ts — the property isuntyped
Surprising behaviour:
error() renders +error.svelte instead of the page, so the page component never runs at allredirect() after a POST should use 303, or the browser repeats the POST at the new URLunfounded
await parent() before an independent query serialises what could have been paralleluse:enhance reloads the whole page, which is correct behaviour rather than a bugpage from $app/stores still works and is the pre-Svelte-5 form; $app/state is the current onepath: '/' may not be sent back on the next request.remote.ts) are experimental behind kit.experimental.remoteFunctionsMore gotchas, the load-function input matrix and the import surface are in reference.md.
</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.