SvelteKit 5: 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')}, ] Final structure can look like: router/ [...path]/ page.js page.server.js +page.svelte +error.svelte router.js 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; 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 }, }, }); 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; } } [...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; } [...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; } [...path]/+page.svelte: import {route} from '/router/router.js'; export let data; {#snippet draw(route, index)} {#if route.layouts.length && index { let pattern = Router.get_pattern(page.url.pathname); Error = await Router.get_error(pattern); }); {#if Error} {/if}

Mar 11, 2025 - 20:41
 0
SvelteKit 5: 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')},
]

Final structure can look like:

router/
    [...path]/
        page.js
        page.server.js
        +page.svelte
        +error.svelte
    router.js

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;

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
        },
    },
});

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;
    }
}

[...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;
}

[...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;
}

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



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

{@render draw($route, 0)}

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



{#if Error}
    
{/if}

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



{$route.slugs.id}

That's all