DEV Community

Max Core
Max Core

Posted on

2 1 1

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')},
]

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

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay