Learn more about svelte
If you would like to know more about svelte, click the link!
SvelteKit is a framework for building websites with Svelte. If Svelte is the tool that lets you build reactive components, SvelteKit adds the rest of what you need for a real website: routing between pages, server-side rendering (SSR) for faster load times and better SEO, and the ability to load data before a page displays.
Think of it like this: Svelte handles the component — the reusable pieces of your UI — and SvelteKit handles the app — the pages, navigation, and data flow. You write .svelte files that contain HTML, CSS, and JavaScript all in one place, just like a regular web page, and SvelteKit turns them into an optimized production site.
Before you start, make sure you have Node.js installed. Node.js gives you npm, the package manager you'll use to create and run your project.
Step 1 — Create a new project:
Open your terminal, navigate to the folder where you want your project to live, and run:
npx sv create my-appThis starts the Svelte CLI tool. It will ask you a few questions — choose the SvelteKit option, then pick a template ("Demo" gives you a sample app, "Skeleton" starts completely empty). It will install all dependencies automatically.
Step 2 — Start the development server:
cd my-app
npm run devThis starts a local server at http://localhost:5173. Open that URL in your browser. Any change you make to your code will appear instantly without refreshing the page — that's hot module reloading (HMR).
Step 3 — Build for production:
When you're ready to share your site, run:
npm run buildSvelteKit compiles everything into static HTML, CSS, and JS inside a build folder that you can deploy anywhere.
When you create a SvelteKit project, you'll see this basic folder layout that matters most:
my-app/
├── src/
│ ├── routes/ # Each file here becomes a page on your site
│ │ ├── +page.svelte # The homepage (yourdomain.com/)
│ │ ├── +layout.svelte # Wraps every page (nav, footer, etc.)
│ │ └── +error.svelte # Shown when something goes wrong
│ ├── lib/ # Reusable components and modules
│ │ └── components/ # Your .svelte component files
│ ├── app.html # The HTML shell that every page loads into
│ └── app.css # Global styles
├── static/ # Files served as-is (images, fonts, favicon)
├── package.json
├── svelte.config.js
└── vite.config.jsThe key convention — routing via files:
SvelteKit uses file-based routing. A file at src/routes/about/+page.svelte automatically becomes yourdomain.com/about. Folders create URL segments, and special + prefix files define what each route does:
+page.svelte — A page. One per route.
+layout.svelte — A wrapper that surrounds all pages inside its folder and sub-folders. Great for shared navigation or sidebars.
+error.svelte — An error boundary for that route and its children.
Everything inside src/lib is private to your app. You import from it using the $lib alias — for example, import Button from '$lib/components/Button.svelte'.
A Svelte component is a single .svelte file that contains all three parts of a web piece in one place: the JavaScript (logic), the HTML (structure), and the CSS (style).
<script>
let name = 'world';
</script>
<h1>Hello {name}!</h1>
<style>
h1 {
color: purple;
font-family: sans-serif;
}
</style>Notice a few things:
🔹 The <script> tag holds your JavaScript. You declare variables, import other components, and write logic here.
🔹 In the HTML section, you use {curly braces} to output JavaScript values — {name} becomes world.
🔹 The <style> tag is scoped by default. The CSS inside only applies to this component — it won't leak out and affect other parts of the page. This is a huge benefit: no more class name conflicts.
🔹 To apply a global style (one that applies everywhere), wrap it in :global(...): :global(body) { margin: 0; }
In a normal JavaScript variable, if you change the value, the HTML doesn't update — it stays whatever it was when the page first loaded. Reactive state fixes that: when the value changes, Svelte automatically updates the parts of the page that use it.
You declare reactive state with the $state rune:
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>
Clicks: {count}
</button>Every time you click the button, count increases by one, and the displayed number updates automatically. You don't need to manually re-render anything.
What can be reactive? Any JavaScript value: numbers, strings, booleans, arrays, and objects. Arrays and objects become deeply reactive — changing a nested property or pushing to an array also triggers updates:
<script>
let todos = $state(['learn svelte', 'build something']);
function addTodo() {
todos.push('another task'); // This updates the UI
todos = todos; // Trigger the reactivity
}
</script>Note: In Svelte 5, arrays and objects need to be reassigned after mutation (as shown above) to trigger updates. Using todos = [...todos, 'new'] (creating a new array) also works.
Often you need a value that is computed from other reactive state. For example, a full name from a first and last name, or a total price from a list of items. That's what $derived is for.
<script>
let firstName = $state('Jane');
let lastName = $state('Doe');
let fullName = $derived(firstName + ' ' + lastName);
</script>
<p>Hello, {fullName}</p>fullName will automatically recompute whenever firstName or lastName changes. You never need to manually update it.
For more complex logic — like operations that need multiple lines or an array method — use $derived.by with a function:
<script>
let cart = $state([2.99, 5.50, 1.25]);
let total = $derived.by(() => {
return cart.reduce((sum, price) => sum + price, 0);
});
</script>
<p>Total: ${total.toFixed(2)}</p>A derived value is read-only — you can't assign to it. It always follows from the state it depends on.
Props are how you pass data from a parent component down into a child component — just like HTML attributes.
Receiving props in a child component:
<!-- Greeting.svelte -->
<script>
let { name, age } = $props();
</script>
<p>{name} is {age} years old.</p>Passing props from a parent:
<!-- Parent.svelte -->
<script>
import Greeting from './Greeting.svelte';
</script>
<Greeting name="Alice" age={30} />You can set default values so the component still works if a prop is omitted:
let { name = 'Guest', age = 0 } = $props();You can also use a rest pattern to collect remaining props and spread them onto an element:
<script>
let { class: className, ...others } = $props();
</script>
<button {...others}>Click me</button>This is useful when you want a component to pass through native HTML attributes like id, disabled, or event handlers.
Sometimes you need to run code when reactive state changes — like logging a value, saving to localStorage, or starting an animation. That's what $effect is for.
<script>
let count = $state(0);
$effect(() => {
// This runs whenever 'count' changes
console.log('Count is now', count);
});
</script>
<button onclick={() => count++}>
{count}
</button>The function inside $effect runs immediately when the component mounts, and then re-runs every time any reactive value it accesses changes. Svelte figures out the dependencies automatically just by looking at which $state or $derived values are used inside the function.
Cleanup: If your effect sets up something that needs to be torn down (an interval, an event listener, a GSAP animation), return a cleanup function:
<script>
let seconds = $state(0);
$effect(() => {
const interval = setInterval(() => {
seconds++;
}, 1000);
// Cleanup — runs when the component unmounts
// or before the effect runs again
return () => clearInterval(interval);
});
</script>
<p>{seconds}s elapsed</p>$effect.pre — Runs before the DOM is updated. Use this when you need to read the old DOM state before Svelte applies changes (for example, measuring scroll position before adding new content).
Snippets let you reuse chunks of markup inside the same component without creating a separate file. Think of them like a function for HTML.
<script>
let pets = ['dog', 'cat', 'bird'];
</script>
{#snippet petEmoji(name)}
<span>
{#if name === 'dog'} 🐕
{:else if name === 'cat'} 🐈
{:else} 🐦
{/if}
</span>
{/snippet}
<ul>
{#each pets as pet}
<li>
{@render petEmoji(pet)}
{pet}
</li>
{/each}
</ul>You define a snippet with {#snippet name(params)}...{/snippet} and render it with {@render name(args)}.
Snippets can also be passed to other components as props. This is useful when you want a child component to control the structure but let the parent control the content:
<!-- Parent passes a snippet to Table -->
{#snippet header()}
<th>Name</th>
<th>Price</th>
{/snippet}
{#snippet row(item)}
<td>{item.name}</td>
<td>\${item.price}</td>
{/snippet}
<Table data={products} {header} {row} />Children as snippets: Content placed between a component's opening and closing tags automatically becomes a children snippet:
<Card>
<p>This is the card body.</p>
</Card>
<!-- Card.svelte -->
<script>
let { children } = $props();
</script>
<div class="card">
{@render children()}
</div>Routing in SvelteKit is built around the file system. Every <>.svelte file or folder inside src/routes creates a URL on your site.
Static routes:
src/routes/
├── +page.svelte → /
├── about/
│ └── +page.svelte → /about
├── contact/
│ └── +page.svelte → /contactDynamic routes (with parameters):
Wrap a folder name in square brackets to create a dynamic segment:
src/routes/threejs-blog/
├── +page.svelte → /threejs-blog
├── [id]/
│ └── +page.svelte → /threejs-blog/1, /threejs-blog/hello-world, etc.Inside the [id] page, you access the parameter via $page.params.id or from the load function.
Layouts — shared UI for multiple pages:
A +layout.svelte file wraps all pages inside its folder (and sub-folders). Use it for navigation bars, footers, or sidebars that should appear on every page:
<!-- src/routes/+layout.svelte -->
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main>
{@render children()}
</main>
<footer>© 2026</footer>The layout receives a children snippet (the page content) and renders it where you place {@render children()}.
Loading data before a page renders:
Create a +page.server.ts (or .js) file alongside your +page.svelte to fetch data on the server before the page loads:
// src/routes/threejs-blog/+page.server.ts
export function load() {
return {
posts: getPosts() // fetch from a database or API
};
}The returned data becomes the data prop in your page component:
<script>
let { data } = $props();
</script>
<h1>Blog</h1>
{#each data.posts as post}
<article>{post.title}</article>
{/each}Error pages: A +error.svelte file in any route folder creates an error boundary that displays when something goes wrong in that section of the app. The root +error.svelte catches errors anywhere.
If you would like to know more about svelte, click the link!