DEV Community

Max Core
Max Core

Posted on • Edited on

1 3 1 1

SvelteKit 2: How to make code-based router, instead of file-based router

The idea is to create universal [...path] that will capture all, and our urls.js will look like this:

export const default_error = () => import('/src/error.svelte');

const base_layout = {page: () => import('/src/base.svelte'), default_error}

export const patterns = [
    {re: /^\/\/?$/,                                 page: () => import('/src/home.svelte'),    layouts: [base_layout], js: import('/src/home.js')},
    {re: /^\/about\/?$/,                            page: () => import('/src/about.svelte'),   layouts: [base_layout], endpoint: import('/src/about.js')},
    {re: /^\/article\/([0-9]+)\/?$/, slugs: ['id'], page: () => import('/src/article.svelte'), layouts: [base_layout], js: import('/src/article.js'),  endpoint: import('/src/article_endpoint.js')},
]

Enter fullscreen mode Exit fullscreen mode

Final structure can look like:

router/
    [...path]/
        page.js
        page.server.js
        +page.svelte
        +error.svelte
    router.js
Enter fullscreen mode Exit fullscreen mode

Since router/ is renamed routes/ putted in project root along with src/, we have to specify it in configs:

svelte.config.js:

import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
const config = {
    preprocess: vitePreprocess(),
    kit: {
        adapter: adapter(),

        // Add this:
        files: {
            routes: 'router/',
        },
    }
};
export default config;
Enter fullscreen mode Exit fullscreen mode

vite.config.js:

import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';

export default defineConfig({
    plugins: [sveltekit()],
    server: {

        // Add this:
        fs: {
            allow: ['..'],  // Allow serving files from one level up to the project root
        },
    },
});
Enter fullscreen mode Exit fullscreen mode

Next, just contents of router/ folder

router.js:

import { writable } from 'svelte/store';
import { error } from '@sveltejs/kit';
import { patterns, default_error } from '/urls.js';

export const route = writable();
export const Router = {}

Router.get_pattern = function(pathname) {
    for (const pattern of patterns) {
        if (pattern.re.test(pathname)) {
            return pattern;
        }
    }
}

Router.check_404 = function(pattern) {
    if (!pattern) {
        throw error(404, 'Not found')
    }
}

Router.call_loads = async function(params, pattern, type) {
    for (const page of [pattern, ...(pattern.layouts || [])]) {
        if (!page[type]) continue;
        const load = (await page[type]()).load;
        params.data = {...params.data, ...await load(params)};
    }
    return params.data;
}

Router.get_route = async function(pattern, url) {
    const route = {}
    route.pattern = pattern;
    route.url = url;
    route.slugs = Router.get_slugs(pattern, url);
    route.error = await Router.get_error(pattern)
    return route;
}

Router.get_templates = async function(params, pattern) {
    const templates = {}
    templates.page = (await pattern.page()).default;
    templates.layouts = [];
    for (const layout of pattern.layouts || []) {
        templates.layouts.push((await layout.page()).default);
    }
    return templates;
}

Router.get_slugs = function(pattern, url) {
    const slugs = {}
    const matches = pattern.re.exec(url.pathname);
    for (const [index, match] of Object.entries(matches)) {
        const int_index = parseInt(index);
        if (int_index && int_index > 0) {
            slugs[pattern.slugs[index - 1]] = match
        }
    }
    return slugs;
}

Router.get_error = async function(pattern) {
    if (!pattern) return (await default_error()).default;
    for (const page of [...(pattern.layouts || []), pattern].reverse()) {
        if (!page.error) continue;
        const error = (await page.error()).default;
        return error;
    }
}
Enter fullscreen mode Exit fullscreen mode

[...path]/page.js:

import { Router, route } from '/router/router.js';

export async function load(params) {

    const pattern = Router.get_pattern(params.url.pathname);
    const raw_route = await Router.get_route(pattern, params.url);
    route.update((v) => ({...v, ...raw_route}));

    params.data = await Router.call_loads(params, pattern, 'js');

    const templates = await Router.get_templates(params, pattern);
    route.update((v) => ({...v, ...templates}));

    return params.data;
}
Enter fullscreen mode Exit fullscreen mode

[...path]/page.server.js

import { Router, route } from '/router/router.js';

export async function load(params) {

    const pattern = Router.get_pattern(params.url.pathname);
    Router.check_404(pattern);
    const raw_route = await Router.get_route(pattern, params.url);
    route.update((v) => ({...v, ...raw_route}));

    params.data = await Router.call_loads(params, pattern, 'server');

    return params.data;
}
Enter fullscreen mode Exit fullscreen mode

[...path]/+page.svelte:

<script>
    import {route} from '/router/router.js';
    export let data;
</script>

{#snippet draw(route, index)}
    {#if route.layouts.length && index < route.layouts.length}
        <svelte:component this={route.layouts[index]} {data}>
            {@render draw(route, index + 1)}
        </svelte:component>
    {:else}
        <svelte:component this={route.page} {data}/>
    {/if}
{/snippet}

{@render draw($route, 0)}
Enter fullscreen mode Exit fullscreen mode

[...path]/+error.svelte:

<script>
    import { Router, route } from '/router/router.js';
    import { page } from '$app/state';

    let Error = $state($route?.error);

    $effect(async() => {
        let pattern = Router.get_pattern(page.url.pathname);
        Error = await Router.get_error(pattern);
    });
</script>

{#if Error}
    <Error/>
{/if}
Enter fullscreen mode Exit fullscreen mode

Now, we just have global route store, that stores current slugs, matched page, etc.:

article.js:

<script>
    import {route} from '/router/router.js';
</script>

{$route.slugs.id}
Enter fullscreen mode Exit fullscreen mode

That's all

Quadratic AI

Quadratic AI – The Spreadsheet with AI, Code, and Connections

  • AI-Powered Insights: Ask questions in plain English and get instant visualizations
  • Multi-Language Support: Seamlessly switch between Python, SQL, and JavaScript in one workspace
  • Zero Setup Required: Connect to databases or drag-and-drop files straight from your browser
  • Live Collaboration: Work together in real-time, no matter where your team is located
  • Beyond Formulas: Tackle complex analysis that traditional spreadsheets can't handle

Get started for free.

Watch The Demo 📊✨

Top comments (2)

Collapse
 
subhendupsingh profile image
Subhendu Pratap Singh

It's Svelte 5 not Sveltekit 5. Sveltekit is on 2.20.x

Collapse
 
maxcore profile image
Max Core

ty

Jetbrains Survey

Calling all developers!

Participate in the Developer Ecosystem Survey 2025 and get the chance to win a MacBook Pro, an iPhone 16, or other exciting prizes. Contribute to our research on the development landscape.

Take the survey

AWS Security LIVE!

Hosted by security experts, AWS Security LIVE! showcases AWS Partners tackling real-world security challenges. Join live and get your security questions answered.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️