DEV Community

Cover image for Getting Started with SvelteKit: Setup, Project Structure & First App
Ali Aslam
Ali Aslam

Posted on

Getting Started with SvelteKit: Setup, Project Structure & First App

So you’re convinced that Svelte is worth your time (if not, go back and re-read previous article in the series 👀). Now it’s time to actually spin up a Svelte app and see it in action.

By the end of this article, you’ll have:

  • A working SvelteKit project running locally.
  • A solid understanding of the project structure.
  • The confidence to poke around without fear of breaking stuff.

Let’s dive in. 🌊


Table of Contents


Step 1: Installing SvelteKit

Gone are the days of mysterious webpack configs and 50-line setup guides. SvelteKit makes it almost too easy.

👉 You’ll need Node.js v18+ installed. It includes npm, which we’ll use to install and run packages like Svelte. Svelte (and SvelteKit) are distributed as npm packages you add to a project.

With that ready, open your terminal and run:

npx sv create hello-svelte
Enter fullscreen mode Exit fullscreen mode

This uses the official Svelte CLI (sv) to scaffold your project. It replaces the old npm create svelte@latest flow.


1) Choose a template

  • SvelteKit minimal → clean, barebones setup (use this for the tutorial) ✅
  • SvelteKit demo → comes with example code
  • Svelte library → for publishing reusable components (not needed here)

2) Choose a language

You’ll be asked which language you want to use: JavaScript or TypeScript.

  • For this series, select JavaScript (we’ll keep things plain and simple).
  • If you prefer TypeScript, pick it here—the CLI sets it up for you. You can always consult Svelte’s TS docs later if you switch.

3) Pick extras (integrations)

You’ll see a checklist like:

[•] prettier
[ ] eslint
[ ] vitest
[ ] playwright
[ ] tailwindcss
[ ] sveltekit-adapter
[ ] devtools-json
[ ] drizzle
[ ] lucia
[ ] mdsvex
[ ] paraglide
[ ] storybook
Enter fullscreen mode Exit fullscreen mode

Recommended for this tutorial:

  • Prettier → ✅ keep checked (auto-formats your code)
  • Everything else → skip for now (we’re starting lean; you can add tools later)

4) Package manager

  • npm → ✅ recommended (comes with Node.js)
  • yarn / pnpm / bun / deno → fine if you already use them
  • None → skips installing dependencies

Choose npm and the CLI will auto-install dependencies for you. If you chose None, install manually:

cd hello-svelte
npm install
Enter fullscreen mode Exit fullscreen mode

5) Run the app

The CLI prints the next steps:

📁 Project steps
   1: cd hello-svelte
   2: npm run dev -- --open
Enter fullscreen mode Exit fullscreen mode

You can do it now, or from within the editor (VSCode terminal) after reading the next part.

This starts the dev server and opens your browser. Your app runs at http://localhost:5173. Press Ctrl + C to stop.


🛠 Editor Setup (Recommended)

Before you start coding, let’s make sure your editor understands Svelte.

Use VS Code → It’s free, popular, and has the best Svelte support.

  1. Install Visual Studio Code.
  2. In VS Code, go to the Extensions Marketplace and add:
  • Svelte for VS Code (official extension — gives you autocomplete, IntelliSense, error checking).
  • Prettier (if you selected Prettier in the setup wizard, this ensures consistent formatting).
  • ESLint (optional, if you enabled ESLint in the wizard).

Now your editor will understand Svelte syntax and give you autocomplete. Restart VS Code if things don’t kick in right away.

Now you can step into project folder i.e. cd hello-svelte and issue command code . to bring up visual studio code open in context of your project folder. (Don't forget the '.' which stands for current folder). You can also open the folder from file menu.

To execute the npm run dev command from within visual studio code, use the Terminal->New terminal menu option. And then issue the command from there.


Step 2: The Project Structure

Before we start wildly coding, let’s peek under the hood. Here’s what a fresh SvelteKit minimal project looks like today:

hello-svelte/
├── src/
│   ├── routes/
│   │   └── +page.svelte
│   └── lib/
├── static/
├── .gitignore
├── .npmrc
├── jsconfig.json
├── package.json
├── package-lock.json
├── svelte.config.js
├── vite.config.js
└── README.md
Enter fullscreen mode Exit fullscreen mode

Looks neat, right? Let’s break it down:


📂 src/ → Your main playground

  • routes/ → File-based routing. Each +page.svelte is a page.

    • +page.svelte → A page component (the + is part of the convention).
    • +layout.svelte → (optional) shared layout for nested routes (headers, footers, navbars, etc.).
  • lib/ → For reusable code: components, stores, utilities.


📂 static/

Any files in here (images, icons, robots.txt, etc.) are served as-is at the root of your site.


⚙️ Config & meta files

  • svelte.config.js → SvelteKit’s main config (adapters, preprocessors, etc.). You’ll touch this later when deploying or adding integrations.
  • vite.config.js → Vite bundler config. Usually fine as-is, but you can tweak it for advanced use cases.
  • jsconfig.json → Helps your editor with IntelliSense and path aliases ($lib points to src/lib).
  • .npmrc → Locks npm-specific settings (e.g. ensuring consistent installs across machines).
  • .gitignore → Tells Git which files/folders to skip (like node_modules/ or .svelte-kit/).
  • package.json / package-lock.json → Dependency list and scripts (start, dev, build, etc.), like any Node.js project.
  • README.md → Quick notes about your project.

⚡ Notice what’s missing compared to older tutorials:

  • app.html — this used to be editable, but in modern SvelteKit it’s handled internally. You rarely, if ever, need to touch the HTML shell anymore.

That’s the high-level view. Next, we’ll poke around inside src/routes/ and see how pages and layouts actually work.


Step 3: Routes & Pages — The Magic of File-Based Routing

In most frameworks, you have to set up a router, configure paths, and map components manually.
In SvelteKit? You just make files. 🎉

SvelteKit uses file-based routing: the structure of the src/routes/ folder defines the URLs in your app.

Here’s what you get in a fresh project:

src/
└── routes/
    └── +page.svelte   → http://localhost:5173/
Enter fullscreen mode Exit fullscreen mode

That +page.svelte is your home page.

💡 Why the plus sign?
Files that start with + are special SvelteKit route files (+page.svelte, +layout.svelte, etc.).
The + makes them easy to spot and prevents conflicts with your own files.
Anything without + is just normal code you can write freely.


🗂️ Naming rules you should know

  • +page.svelte → a Svelte component that renders when you visit that route.
  • +layout.svelte → optional wrapper for shared UI (like navbars/footers). We'll explore it further in next step.
  • +page.server.js / +page.server.ts → server-side data loading for that page.
  • +layout.server.js → server-side data loading for layouts.
  • +error.svelte → error boundary for that route.
  • Folders → create subroutes. A folder becomes part of the URL path.

👉 Don’t worry if some of these sound confusing right now.
For the moment, you only need to focus on +page.svelte (pages) and +layout.svelte (layouts). We’ll explore the others (server data, error handling) later in the series.

So:

src/routes/about/+page.svelte
Enter fullscreen mode Exit fullscreen mode

…creates a new page at:

http://localhost:5173/about
Enter fullscreen mode Exit fullscreen mode

And if you add:

src/routes/blog/+page.svelte
Enter fullscreen mode Exit fullscreen mode

…you’ll get:

http://localhost:5173/blog
Enter fullscreen mode Exit fullscreen mode

Let’s try it out:

Inside src/routes/, create a file called:

about/+page.svelte
Enter fullscreen mode Exit fullscreen mode

Drop in some code:

<script>
  let team = $state(["Ali", "Bob", "Caleb"]);
</script>

<h1>About Us</h1>
<ul>
  {#each team as person}
    <li>{person}</li>
  {/each}
</ul>
Enter fullscreen mode Exit fullscreen mode

Don't worry about the code itself for now. This would be covered in detail later. For now you can copy and paste, or best, type in the code. In my experience, building muscle memory by typing is the best way to learn a new language. But it's your call.

Now visit http://localhost:5173/about.
💥 Boom — you have a new page. No router config. No imports. Just the file.

That’s file-based routing: the folder structure = your URL structure.


Step 4: Layouts — Reusing UI Across Pages

Most apps have common stuff on every page — navbars, footers, sidebars. In SvelteKit, that’s what +layout.svelte is for.

When you created the project, SvelteKit already gave you a layout file:

src/routes/+layout.svelte

<script>
    import favicon from '$lib/assets/favicon.svg';

    let { children } = $props();
</script>

<svelte:head>
    <link rel="icon" href={favicon} />
</svelte:head>

{@render children?.()}
Enter fullscreen mode Exit fullscreen mode

🧩 What’s going on here?

  • $props() → gives you the props that SvelteKit automatically passes into this layout.
  • let { children } = $props(); → extracts the special children prop.
  • children → represents the current page being displayed.
  • {@render children?.()} → actually renders that page at this spot inside the layout.

💡 In other words: wherever you put {@render children?.()}, that’s where the current page goes.


🕰️ Optional clarification: Old vs. new SvelteKit (Optional: for people familiar with old svelte syntax)

Older versions of svelte use <slot />. In modern SvelteKit (v2+), that’s been replaced with the children prop.

They mean the same thing:

  • <slot /> (old) → “insert the page here.”
  • {@render children?.()} (new) → “insert the page here.”

🔄 What happens when you navigate?

When you move between / and /about, the layout stays the same.

  • The <nav> and anything else in +layout.svelte is persistent.
  • Only the children (the current page) changes.

Visualized:

+layout.svelte
 ├── <nav> ... </nav>        ← stays put
 └── {@render children?.()}  ← replaced with the current page
        ├─ /       → renders src/routes/+page.svelte
        └─ /about  → renders src/routes/about/+page.svelte
Enter fullscreen mode Exit fullscreen mode

✨ Let’s customize it

Let’s add a navbar so it’s shared across all pages:

<script>
    import favicon from '$lib/assets/favicon.svg';
    let { children } = $props();
</script>

<svelte:head>
    <link rel="icon" href={favicon} />
</svelte:head>

<nav>
  <a href="/">Home</a> |
  <a href="/about">About</a>
</nav>

{@render children?.()}
Enter fullscreen mode Exit fullscreen mode

Now you would see a navigation bar at top of the page, and when you navigate between / and /about, the navbar stays put, while the children (page content) swaps out.

If earlier section about layout/navigation confused you, you can read it again and it would make total sense now.

👉 No need for <Router> wrappers or Outlet components — layouts are built in. Simple, clean, elegant. ✨


Step 5: Nested Layouts (Optional but Powerful)

Layouts can nest inside each other, which is super useful for organizing sections of your app.

For example, say you have a dashboard with multiple pages under /dashboard.
Create a new layout just for that section:

src/routes/dashboard/+layout.svelte
Enter fullscreen mode Exit fullscreen mode
<script>
    let { children } = $props();
</script>

<section>
  <h2>Dashboard</h2>
  <nav>
    <a href="/dashboard">Overview</a> |
    <a href="/dashboard/settings">Settings</a>
  </nav>

  {@render children?.()}
</section>
Enter fullscreen mode Exit fullscreen mode

Tip: In Visual Studio Code, you can select 'routes' folder and create new file as dashboard/+layout.svelte, and it would automatically place +layout.svelte under dashboard folder (and create dashboard folder if it didn't exist already)


Add some child pages

src/routes/dashboard/+page.svelte

<h3>Dashboard Overview</h3>
<p>Welcome to your dashboard.</p>
Enter fullscreen mode Exit fullscreen mode

src/routes/dashboard/settings/+page.svelte

<h3>Dashboard Settings</h3>
<p>Here you can update your preferences.</p>
Enter fullscreen mode Exit fullscreen mode

How it works

Now when you visit:

  • /dashboard → you’ll see the global navbar (from root layout), then the Dashboard heading + section nav, and finally the Overview page.
  • /dashboard/settings → the global navbar and the dashboard layout stay put, only the page content (Settings) swaps out.

Visualized:

+layout.svelte (root)
 ├── Global navbar (always visible)
 └── {@render children?.()}
       └── +layout.svelte (dashboard)
            ├── Dashboard title + nav (always visible inside dashboard)
            └── {@render children?.()}
                 ├── +page.svelte → /dashboard
                 └── settings/+page.svelte → /dashboard/settings
Enter fullscreen mode Exit fullscreen mode

👉 Notice how the dashboard layout is nested inside the root layout. That’s why your section nav appears under the main navbar by default — unless you style it differently with CSS.


Step 6: The lib/ Folder — Your Personal Toolbox 🧰

The src/lib/ folder in SvelteKit is special: it’s where you put reusable code that multiple pages will need.

Think of it as your toolbox. Inside lib, you’ll usually keep:

  • Components → buttons, modals, navbars
  • Stores → shared state (like logged-in user info)
  • Utils → helper functions

🤔 But wait… isn’t lib/ usually output?

If you’ve worked with plain JavaScript, Vite, or Rollup before, you might be used to seeing lib/ (or dist/, build/) as an output folder where compiled bundles go.

SvelteKit flips that convention on its head:

  • In SvelteKit, src/lib/ is source code you write, not build output.
  • Why? Because SvelteKit is designed to feel like an app and a package at the same time. By keeping reusable code in lib, you could later publish parts of it (e.g. components or utilities) as an npm package if you wanted.

👉 Mental shift: src/lib/ is input, not output. Treat it like your personal library of building blocks.


Example: a Counter component

Let’s build a simple counter as your very first reusable component.

Create a new file:

src/lib/Counter.svelte
Enter fullscreen mode Exit fullscreen mode
<script>
  // This is a normal JavaScript variable
  // but marked with $state so Svelte knows:
  // “when this changes, update the UI.”
  let count = $state(0);
</script>

<!-- Here we *use* that variable in the UI -->
<button onclick={() => count++}>
  Count: {count}
</button>
Enter fullscreen mode Exit fullscreen mode

Use it in the home page

Now let’s try it out. Open your home page (src/routes/+page.svelte) and add the counter:

<script>
  import Counter from '$lib/Counter.svelte';
</script>

<h1>Welcome to SvelteKit</h1>
<p>Here’s your first component in action:</p>

<Counter />
Enter fullscreen mode Exit fullscreen mode

When you click the button, the number goes up. 🎉


What’s going on here?

  • In the <script> section of Counter.svelte, you declared a variable (count) and told Svelte it should update the UI when it changes.
  • In the markup, you used {count} inside the button — so every time count changes, the text updates automatically.
  • In +page.svelte, you imported the Counter component and wrote <Counter />.

    • That’s just a custom HTML element provided by your component.
    • Think of it like <button> or <h1>, except you created it yourself.
    • This is how you reuse components in Svelte — build once, drop them anywhere.

💡 Quick note on $state:
You might notice we wrote let count = $state(0) instead of let count = 0.
Both still work today, but $state is the recommended way because it makes it clear which variables should update the UI. At the moment it's a suggested nudge, but in later versions the support might be dropped altogether.

💡 Pro tip: Want to keep your homepage clean? Create a new route (src/routes/counter/+page.svelte) and put the <Counter /> there instead. That way your counter demo lives on its own page.


🚀 Best practices for lib/ organization

As your app grows, lib/ can get messy if you throw everything in the root. Common practice is to add subfolders:

src/lib/
├── components/
│   ├── Counter.svelte
│   ├── Button.svelte
│   └── Navbar.svelte
├── stores/
│   └── user.js
├── utils/
│   └── formatDate.js
└── assets/
    └── favicon.svg
Enter fullscreen mode Exit fullscreen mode
  • components/ → all your Svelte components
  • stores/ → global state (using Svelte stores)
  • utils/ → helper JS functions
  • assets/ → icons, images, static files you want to import in code (different from /static, which is for files served as-is)

💡 With this structure, your imports stay clean thanks to the $lib alias:

import Counter from '$lib/components/Counter.svelte';
import { user } from '$lib/stores/user.js';
import formatDate from '$lib/utils/formatDate.js';
Enter fullscreen mode Exit fullscreen mode

👉 Key takeaway: src/lib/ is not an output folder. It’s your reusable code folder.
Keeping it well-organized from the start makes your project easier to scale, maintain, and even share as a package later.


Step 7: app.html — The Root Template 🌍

Peek into src/app.html.

This is the raw HTML file that wraps your whole app. You’ll rarely need to touch it, but it’s useful for:

  • Adding meta tags (<meta name="description" ...>).
  • Linking external fonts or stylesheets.
  • Setting a global favicon.

Example: add a Google Font:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap">
Enter fullscreen mode Exit fullscreen mode

Boom, now your whole app uses Roboto. 🎨


Step 8: Static Assets — Where to Put Your Images 📂

Want to add an image to your site? Drop it in static/.

Example: place logo.png in static/.

Now in a Svelte file:

<img src="/logo.png" alt="App Logo" />
Enter fullscreen mode Exit fullscreen mode

No import needed — it’s served directly from /. Super handy for logos, icons, or other non-dynamic assets.


Step 9: Your First Tweak ✨ (Practice Challenge)

You’ve now got the key building blocks:

  • Layouts (+layout.svelte) for shared UI
  • src/lib/ for reusable components

👉 Time to put it into practice.

Challenge:

  • Create a new component in src/lib/ (maybe a Header, Footer, or Navbar).
  • Import it into your root layout (src/routes/+layout.svelte).
  • Style it a little to make it your own.

💡 Need ideas?

  • A Header with a logo and tagline.
  • A Footer with your name and the year.
  • Move your <nav> into its own Navbar.svelte.

There’s no “right” answer here — the goal is just to practice using layouts + lib components together.


Wrapping Up

Let’s recap what we covered today:

  • ✅ Spun up a SvelteKit project.
  • ✅ Explored routes & file-based routing.
  • ✅ Learned about layouts (global + nested).
  • ✅ Discovered the lib/ folder, app.html, and static/.
  • ✅ Built our first reusable component.

That’s a lot of ground covered — you now understand how a SvelteKit project is structured and how pages/layouts flow together.

Note: All the coding you’ve done so far—building pages, layouts, and library components—is designed as a warm-up, not your final implementation. You’re getting hands-on familiarity with the structure and flow of a SvelteKit app. We’ll revisit and refine these concepts as we go deeper in later articles.

In the next article (“Reactivity 101 — The \$ Magic in Svelte”), we’ll dive into what makes Svelte really shine: its reactivity model. That’s where the fun begins. 🎇

Top comments (0)