frontend/svelte-project-starter/SKILL.md
Scaffold a SvelteKit 2.x project with Svelte 5 runes, file-based routing, load functions, form actions, and adapter configuration for multiple deployment targets.
npx skillsauth add achreftlili/deep-dev-skills svelte-project-starterInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Scaffold 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 install
The 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-cloudflare
For 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 mode
npm 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.testing
Set up Vitest 2.x with TypeScript for unit and component testing using test/describe/it, vi.fn/vi.mock/vi.spyOn, component testing with Testing Library, coverage (v8/istanbul), workspace config, and snapshot testing.
testing
Set up pytest 8.x with Python for unit and integration testing using fixtures (scope, autouse, parametrize), async tests (pytest-asyncio), mocking (unittest.mock, pytest-mock), coverage (pytest-cov), conftest.py patterns, and markers.
testing
Set up Playwright 1.49+ with TypeScript for E2E testing using page object model, fixtures, test.describe/test blocks, assertions, selectors, network mocking, CI configuration, and trace viewer.
testing
Set up Jest 30+ with TypeScript for unit tests, integration tests, mocking (jest.fn, jest.mock, jest.spyOn), coverage configuration, custom matchers, snapshot testing, and setup/teardown patterns.