Scaffold a SvelteKit 2.x project with Svelte 5 runes, file-based routing, load functions, form actions, and adapter configuration for multiple deployment targets.
77
72%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Optimize this skill with Tessl
npx tessl skill review --optimize ./frontend/svelte-project-starter/SKILL.mdScaffold a SvelteKit 2.x project with Svelte 5 runes, file-based routing, load functions, form actions, and adapter configuration for multiple deployment targets.
npx sv create my-app
# Select: SvelteKit minimal, TypeScript, ESLint, Prettier, Tailwind CSS
cd my-app
npm installThe sv create CLI will prompt for options. Select:
@tailwindcss/vite)src/
├── routes/
│ ├── +layout.svelte # Root layout (wraps all pages)
│ ├── +layout.server.ts # Root layout server load function
│ ├── +page.svelte # Home page (/)
│ ├── +page.server.ts # Home page server load + form actions
│ ├── +error.svelte # Error page
│ ├── login/
│ │ ├── +page.svelte # /login
│ │ └── +page.server.ts # Login form action
│ ├── dashboard/
│ │ ├── +page.svelte # /dashboard
│ │ ├── +page.server.ts # Dashboard data loader
│ │ └── +layout.svelte # Dashboard layout (sidebar)
│ ├── users/
│ │ ├── +page.svelte # /users
│ │ ├── +page.server.ts # Users list loader
│ │ └── [id]/
│ │ ├── +page.svelte # /users/:id
│ │ └── +page.server.ts
│ └── api/
│ └── health/
│ └── +server.ts # GET /api/health (API route)
├── lib/
│ ├── components/ # Reusable components
│ │ ├── ui/ # UI primitives (Button, Card, etc.)
│ │ └── layout/ # Layout components (Header, Sidebar)
│ ├── stores/ # Svelte stores (writable, derived)
│ ├── services/ # API client functions
│ ├── utils/ # Pure utility functions
│ └── types/ # Shared TypeScript types
├── hooks.server.ts # Server hooks (handle, handleFetch, handleError)
├── app.html # HTML template
├── app.css # Global CSS (Tailwind entry)
└── app.d.ts # App-level type declarations
static/ # Static assets served at /
svelte.config.js # SvelteKit configuration
vite.config.ts # Vite configuration
.env.example # Required env vars template$state, $derived, $effect, $props, and $bindable instead of legacy reactive declarations ($:, let, stores for local state).+page.svelte files define pages. +layout.svelte files wrap child routes. +server.ts files define API endpoints.+page.server.ts (server-only) or +page.ts (universal). Data is passed to the page via the data prop.<form> elements with server-side actions defined in +page.server.ts. Progressive enhancement built-in.$lib alias: src/lib/ is aliased to $lib for clean imports.hooks.server.ts runs middleware-like logic (auth, logging) on every server request.svelte.config.js)import adapter from "@sveltejs/adapter-auto";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
alias: {
$components: "src/lib/components",
},
},
};
export default config;vite.config.ts)import { sveltekit } from "@sveltejs/kit/vite";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [tailwindcss(), sveltekit()],
});src/app.css)@import "tailwindcss";src/routes/+layout.svelte)<script lang="ts">
import "../app.css";
import Header from "$lib/components/layout/Header.svelte";
let { children, data } = $props();
</script>
<div class="min-h-screen bg-gray-50">
<Header user={data.user} />
<main class="p-6">
{@render children()}
</main>
</div>src/routes/+layout.server.ts)import type { LayoutServerLoad } from "./$types";
export const load: LayoutServerLoad = async ({ locals }) => {
return {
user: locals.user ?? null,
};
};<!-- src/routes/dashboard/+page.svelte -->
<script lang="ts">
import type { PageData } from "./$types";
import UserCard from "$lib/components/ui/UserCard.svelte";
let { data }: { data: PageData } = $props();
let searchQuery = $state("");
let filteredUsers = $derived(
data.users.filter((u) =>
u.name.toLowerCase().includes(searchQuery.toLowerCase())
)
);
</script>
<h1 class="mb-4 text-2xl font-bold">Dashboard</h1>
<input
type="text"
bind:value={searchQuery}
placeholder="Search users..."
class="mb-4 rounded border px-3 py-2"
/>
<p class="mb-2 text-sm text-gray-500">
Showing {filteredUsers.length} of {data.users.length} users
</p>
{#each filteredUsers as user (user.id)}
<UserCard {user} />
{:else}
<p class="text-gray-500">No users found.</p>
{/each}// src/routes/dashboard/+page.server.ts
import type { PageServerLoad } from "./$types";
import { error, redirect } from "@sveltejs/kit";
export const load: PageServerLoad = async ({ locals, fetch }) => {
if (!locals.user) {
redirect(302, "/login");
}
const response = await fetch("/api/users");
if (!response.ok) {
error(500, "Failed to load users");
}
const users = await response.json();
return { users };
};<!-- src/lib/components/ui/UserCard.svelte -->
<script lang="ts">
interface User {
id: string;
name: string;
email: string;
}
interface Props {
user: User;
selected?: boolean;
onselect?: (id: string) => void;
ondelete?: (id: string) => void;
}
let { user, selected = false, onselect, ondelete }: Props = $props();
</script>
<div
class="mb-2 rounded border p-3 cursor-pointer"
class:border-blue-500={selected}
class:bg-blue-50={selected}
onclick={() => onselect?.(user.id)}
role="button"
tabindex="0"
>
<p class="font-medium">{user.name}</p>
<p class="text-sm text-gray-500">{user.email}</p>
{#if ondelete}
<button
class="mt-2 text-sm text-red-600 hover:underline"
onclick={(e) => { e.stopPropagation(); ondelete?.(user.id); }}
>
Delete
</button>
{/if}
</div><script lang="ts">
// $state — reactive state (replaces `let x = ...` reactivity)
let count = $state(0);
// $derived — computed values (replaces `$: derived = ...`)
let doubled = $derived(count * 2);
let message = $derived(count === 0 ? "Click to start" : `Count: ${count}`);
// $effect — side effects (replaces `$: { ... }` blocks)
$effect(() => {
document.title = `Count: ${count}`;
});
// $effect with cleanup
$effect(() => {
const interval = setInterval(() => {
count += 1;
}, 1000);
return () => clearInterval(interval);
});
</script>
<button onclick={() => count++} class="rounded bg-blue-600 px-4 py-2 text-white">
{message} (doubled: {doubled})
</button><!-- src/routes/login/+page.svelte -->
<script lang="ts">
import { enhance } from "$app/forms";
import type { ActionData } from "./$types";
let { form }: { form: ActionData } = $props();
</script>
<h1 class="mb-4 text-2xl font-bold">Sign In</h1>
<form method="POST" use:enhance class="flex flex-col gap-4">
<input
name="email"
type="email"
placeholder="Email"
class="rounded border px-3 py-2"
required
/>
<input
name="password"
type="password"
placeholder="Password"
class="rounded border px-3 py-2"
required
/>
{#if form?.error}
<p class="text-sm text-red-600">{form.error}</p>
{/if}
<button type="submit" class="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700">
Sign In
</button>
</form>// src/routes/login/+page.server.ts
import type { Actions } from "./$types";
import { fail, redirect } from "@sveltejs/kit";
export const actions: Actions = {
default: async ({ request, cookies }) => {
const formData = await request.formData();
const email = formData.get("email") as string;
const password = formData.get("password") as string;
if (!email || !password) {
return fail(400, { error: "Email and password are required", email });
}
// Authenticate user
const result = await authenticateUser(email, password);
if (!result.success) {
return fail(401, { error: "Invalid credentials", email });
}
// Set session cookie
cookies.set("session", result.token, {
path: "/",
httpOnly: true,
sameSite: "lax",
secure: true,
maxAge: 60 * 60 * 24 * 7, // 1 week
});
redirect(302, "/dashboard");
},
};+server.ts)// src/routes/api/health/+server.ts
import { json } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";
export const GET: RequestHandler = async () => {
return json({ status: "ok", timestamp: new Date().toISOString() });
};// src/routes/api/users/+server.ts
import { json, error } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";
export const GET: RequestHandler = async ({ locals }) => {
if (!locals.user) {
error(401, "Unauthorized");
}
const users = await getUsers();
return json(users);
};
export const POST: RequestHandler = async ({ request, locals }) => {
if (!locals.user) {
error(401, "Unauthorized");
}
const body = await request.json();
const user = await createUser(body);
return json(user, { status: 201 });
};// src/hooks.server.ts
import type { Handle, HandleServerError } from "@sveltejs/kit";
export const handle: Handle = async ({ event, resolve }) => {
// Read session from cookie
const sessionToken = event.cookies.get("session");
if (sessionToken) {
// Validate token and set user on locals
const user = await validateSession(sessionToken);
event.locals.user = user;
}
const response = await resolve(event);
return response;
};
export const handleError: HandleServerError = async ({ error, event }) => {
console.error("Unhandled error:", error);
return {
message: "An unexpected error occurred",
};
};// src/app.d.ts
declare global {
namespace App {
interface Locals {
user: {
id: string;
email: string;
name: string;
} | null;
}
interface Error {
message: string;
}
interface PageData {
user: App.Locals["user"];
}
}
}
export {};<!-- src/routes/+error.svelte -->
<script lang="ts">
import { page } from "$app/state";
</script>
<div class="flex min-h-screen flex-col items-center justify-center gap-4">
<h1 class="text-4xl font-bold">{page.status}</h1>
<p class="text-gray-600">{page.error?.message ?? "An error occurred"}</p>
<a href="/" class="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700">
Go Home
</a>
</div>// svelte.config.js — Node.js adapter (self-hosted)
import adapter from "@sveltejs/adapter-node";
// svelte.config.js — Vercel adapter
import adapter from "@sveltejs/adapter-vercel";
// svelte.config.js — Static adapter (SSG)
import adapter from "@sveltejs/adapter-static";
// Install the adapter you need:
// npm install -D @sveltejs/adapter-node
// npm install -D @sveltejs/adapter-vercel
// npm install -D @sveltejs/adapter-static
// npm install -D @sveltejs/adapter-cloudflareFor static adapter, add to +layout.ts:
// src/routes/+layout.ts
export const prerender = true; // Prerender all pages.env.example to .env and fill in valuesnpm installnpm run devnpm run check to confirm TypeScript and Svelte diagnostics pass# Development
npm run dev # Start dev server (http://localhost:5173)
# Build
npm run build # Production build (uses configured adapter)
npm run preview # Preview production build locally
# Lint & Format
npm run lint # Run ESLint + Prettier check
npm run format # Auto-format with Prettier
# Type check
npm run check # Run svelte-check (TypeScript + Svelte diagnostics)
npm run check:watch # Watch modenpm install -D vitest) and Playwright for E2E (npm install -D @playwright/test). SvelteKit scaffolds Playwright config by default.+page.server.ts load functions and form actions. These run server-side only. Pair with Prisma or Drizzle ORM.$state, $derived) handle component state. For cross-component state, use Svelte stores (writable, derived) or pass data through load functions.superforms (npm install sveltekit-superforms zod).adapter-auto detects the platform automatically on Vercel, Netlify, and Cloudflare.181fcbc
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.