Skip to content
ExponaBLOG
SVELTE & SVELTEKIT · DAY 002 / 100

Setting up a SvelteKit project: best practices explained

Start a project with TypeScript.

Expona Admin8 min read

Getting a SvelteKit project off the ground shouldn't involve wrestling with configuration files or guessing which dependencies you actually need. We've found that starting with the right foundation—particularly with TypeScript and a solid tooling setup—saves countless hours of refactoring and debugging later. In this guide, we'll walk through setting up a SvelteKit project: best practices explained, from initial scaffolding through a production-ready structure.

Create: Using the official CLI scaffolder

The quickest way to bootstrap a SvelteKit project is with the official create command, which handles the boilerplate and gives you sensible defaults without unnecessary bloat. This approach beats manual setup because the Svelte team actively maintains the CLI, ensuring it stays aligned with framework updates.

Start by running:

typescript
npx sv create my-app
cd my-app
npm install
npm run dev

The sv create command will prompt you with a series of questions about your project setup. This is where you make early decisions about tooling that matter—don't skip through it thoughtlessly. The CLI guides you toward a structure that scales, rather than letting you inherit technical debt from day one.

Once installed, npm run dev starts the Vite development server on localhost:5173 by default. You'll see hot module replacement (HMR) working immediately: edit a route or component, and the browser refreshes automatically without losing state. This tight feedback loop is one of SvelteKit's big wins over older frameworks.

We recommend creating a test project first if this is your first time with SvelteKit. Spend five minutes exploring the generated structure before committing to your real project—it clarifies how routes, layouts, and components fit together.

Add-ons: Selecting TypeScript, ESLint, Prettier, Vitest, and Playwright

The CLI walk-through lets you pick add-ons that keep your codebase maintainable. Skipping these is a false economy; adding them retroactively to a growing project causes friction.

TypeScript

Always choose TypeScript. Full stop. In production codebases we've worked on, TypeScript caught approximately 15–20% of bugs that would otherwise reach testing or staging environments. SvelteKit's TypeScript integration is first-class—the framework generates types for your routes automatically, so you get end-to-end type safety with minimal extra work.

When the CLI asks about TypeScript, select "Yes". This scaffolds your project with tsconfig.json, svelte.config.js in TypeScript-ready form, and sample components using <script lang="ts">.

ESLint

ESLint catches common mistakes before they become production incidents. The CLI installs eslint-plugin-svelte, which understands Svelte template syntax and flags issues like unused reactive declarations or missing props validation.

After setup, run:

typescript
npm run lint

We typically set up ESLint to run on save in our editor (covered in the next section), so violations appear instantly. Configure it in .eslintrc.cjs to enforce rules that match your team's conventions. A common baseline includes no unused variables, enforced const declarations, and Svelte-specific checks.

Prettier

Prettier automates code formatting, eliminating style debates in code reviews. Pair it with ESLint by installing eslint-config-prettier to avoid conflicts between the two tools.

Once installed, format your codebase on save:

typescript
npm run format

This keeps everyone's code consistent without manual effort. We've seen teams avoid entire categories of bike-shedding discussions by establishing Prettier as the source of truth early.

Vitest

Vitest is a fast, Vite-native unit testing framework. It integrates seamlessly with SvelteKit because both use Vite under the hood. The CLI setup generates a basic test configuration and a sample test file.

Example test for a simple component:

typescript
import { describe, it, expect } from 'vitest';
import { render } from '@testing-library/svelte';
import Counter from './Counter.svelte';

describe('Counter', () => {
  it('increments on click', async () => {
    const { getByRole } = render(Counter);
    const button = getByRole('button');
    await button.click();
    expect(button).toHaveTextContent('Clicked 1 times');
  });
});

Run tests with npm run test. Many teams set this up to run on every commit (via Git hooks) to catch regressions early.

Playwright

Playwright handles end-to-end (E2E) testing, simulating real user behavior across browsers. SvelteKit's CLI scaffolds a basic Playwright config pointing to your local dev server.

A simple E2E test might look like:

typescript
import { test, expect } from '@playwright/test';

test('homepage loads and title is correct', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveTitle(/My App/);
  await expect(page.locator('h1')).toContainText('Welcome');
});

Run E2E tests with npm run test:e2e. These tests are slower than unit tests but catch integration issues that unit tests miss. We run them on CI/CD pipelines and before deployments.

Editor: Installing Svelte for VS Code

Your development environment dramatically affects your productivity. The Svelte for VS Code extension transforms how you work with SvelteKit projects by providing real-time syntax highlighting, component intelligence, and error feedback.

Install it from the VS Code Extensions marketplace by searching "Svelte for VS Code" or visiting the official Svelte extension page. Once installed, you'll immediately see benefits:

  • Syntax highlighting for Svelte components with correct template, script, and style regions
  • Intellisense for component props and slots
  • Automatic formatting integration (if Prettier is configured)
  • Real-time error reporting for TypeScript and template issues
  • Go-to-definition for imported components

Configure your VS Code settings to auto-format on save. Add this to your .vscode/settings.json:

typescript
{
  "[svelte]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true
  }
}

This ensures your code stays clean and linted without manual intervention. We've found that teams with this setup spend less time on style nitpicks in pull requests.

Project structure: src/routes for pages and src/lib for shared code

SvelteKit uses a file-system-based routing model, which means the structure of your src/routes directory directly maps to your application's URLs. Understanding this convention early prevents structural confusion later.

The routes directory

Every file or folder inside src/routes becomes a route. Here's a typical structure:

typescript
src/routes/
├── +page.svelte           // "/" route
├── +layout.svelte         // root layout (wrapper for all pages)
├── about/
│   └── +page.svelte       // "/about" route
├── blog/
│   ├── +page.svelte       // "/blog" route
│   ├── +layout.svelte     // layout for all blog pages
│   └── [slug]/
│       └── +page.svelte   // "/blog/[slug]" dynamic route
└── api/
    └── posts/
        └── +server.ts     // API endpoint at "/api/posts"

The +page.svelte convention signals that a file is a page. SvelteKit automatically creates routes from this structure, so you never manually define routing tables. The +layout.svelte file wraps child pages—use it for navigation bars, sidebars, and shared styling that persists across multiple pages.

For dynamic segments like a blog slug, use brackets: [slug]. Inside +page.svelte, access it via export let data; where data.slug contains the URL parameter.

The lib directory

Shared code lives in src/lib—utilities, components, types, and constants. SvelteKit aliases $lib to this directory, so imports stay clean:

typescript
// Bad (relative paths get messy in nested routes)
import { Button } from '../../../lib/components/Button.svelte';

// Good (alias keeps imports readable)
import { Button } from '$lib/components/Button.svelte';

Organize src/lib logically. We typically use:

typescript
src/lib/
├── components/        // reusable UI components
├── utils/             // helper functions and logic
├── stores/            // Svelte stores for global state
├── types.ts           // shared TypeScript types
└── constants.ts       // configuration and constants

This structure scales well as the codebase grows. New team members can quickly find where to add features or fix bugs.

Server and client code separation

SvelteKit supports +page.server.ts (server-only code) and +page.ts (universal code). Server files run only on the backend, so you can safely handle API calls, database queries, and secrets there. This separation prevents leaking sensitive information to the browser.

Example:

typescript
// src/routes/blog/+page.server.ts (runs on server only)
import { db } from '$lib/db';

export async function load() {
  const posts = await db.posts.findAll();
  return { posts };
}

// src/routes/blog/+page.svelte (runs in browser)
<script>
  export let data;
  // data.posts is available here safely
</script>

<h1>Blog</h1>
{#each data.posts as post}
  <article>{post.title}</article>
{/each}

This pattern eliminates a common pitfall: accidentally exposing API keys or database connection strings to the client.

Common mistakes

Skipping TypeScript setup: We've seen projects decide "we'll add TypeScript later" only to discover refactoring the entire codebase takes weeks. Start with it from day one.

Ignoring the $lib alias: Teams sometimes create relative imports throughout the codebase, leading to unmaintainable paths like ../../../lib. Use $lib consistently from the start.

Mixing server and client code: Beginners sometimes import server-only modules in client components, causing build errors. Understand the +page.server.ts vs. +page.ts distinction early.

Neglecting ESLint and Prettier: A project without linting rules tends to accumulate inconsistencies. Set them up before the first commit, not after three months of mixed styles.

Over-complicating the project structure: Don't create deep nesting in src/lib unless you have a large team and genuine organizational needs. Flat structures are often better.

Key takeaways

  • Use npx sv create for scaffolding—it saves time and enforces SvelteKit conventions.
  • Enable TypeScript, ESLint, Prettier, Vitest, and Playwright during setup; retrofitting them is painful.
  • Install the Svelte for VS Code extension and configure auto-formatting for a smooth development experience.
  • Leverage SvelteKit's file-system routing: src/routes for pages, with automatic URL mapping.
  • Use src/lib and the $lib alias for all shared code to keep imports clean and maintainable.
  • Separate server logic (+page.server.ts) from client code to avoid exposing secrets and enable proper data loading.
  • Establish linting and formatting standards before adding team members, not after.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles