DEV Community

Max Core
Max Core

Posted on • Updated on

SvelteKit: How to make code-based router, instead of file-based router [December 2022]

Dudes. There are breaking changes in SvelteKit [on December 2022]. That means that my previous post on that topic is garbage now.

For those, who builds enterprise projects, not just landing-pages, and want to name your files and organise your folders based on domains (DDD), or based on technical convenience, rather than on url structure dictated by "marketing needs"; and get full control and flexibility, like — two different urls match one component etc. — please — welcome.

So. After a full day of research and code I've ended up with..

As a first step, let's check what is going on in SvelteKit's depths. Open file:
node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/index.js Navigate to:

292: prevent_conflicts(routes);
Enter fullscreen mode Exit fullscreen mode

And print:

+ 293: console.log(routes);
Enter fullscreen mode Exit fullscreen mode

It'l give us a complex array of objects as the result of defaults walking through file structure.
And that's exactly what we have to define somehow and somewhere in code.

So, what literally have I done...

I. In root of src/ create routes.js...

... where I've places both routes array with custom basic rules and router() function that transforms that rules to array of objects that we've seen in console.log(routes):

const routes = [
    {id: '/',       pattern: /^\/$/,                              page: 'home.svelte',            layout: 'layout.svelte',         segment: ''},
    {id: '/[slug]', pattern: /^\/([^/]+?)\/?$/, params: ['slug'], page: 'article/article.svelte', layout: 'article/layout.svelte', parent_layout_segment: ''}

export function router(routes_dir) {
    const result = []
    for (const route of routes) {
        const parent = result.find(o => o.segment === route.parent_layout_segment)
        const new_route = {
            parent: parent,
            segment: route.segment,
            pattern: route.pattern,
            params: (route.params || []).map((param, index) => ({name: param, matcher: undefined, optional: false, rest: false, chained: false})),
            layout: {
                depth: route.layout_depth || route.depth || 0,
                child_pages: [],
                component:route.layout && routes_dir + route.layout,
                shared: route.layout_js && routes_dir + route.layout_js,
                server: route.layout_server_js && routes_dir + route.layout_server_js,
            error: {
                depth: route.error_depth || route.depth || 0,
                component: route.error && routes_dir + route.error
            leaf: {
                depth: route.leaf_depth || route.depth || 0,
                shared: route.page_js && routes_dir + route.page_js,
                server: route.page_server_js && routes_dir + route.page_server_js,
                component: routes_dir +,
            endpoint: route.server_js && {
                file: routes_dir + route.server_js,
            page: null,  // Have no idea what is it for, but let it be here, just not to forget

    return result;
Enter fullscreen mode Exit fullscreen mode

About params in routes object:

  1. id — need to be unique. Also goes to client manifest.
  2. segment — also need to be unique, and it is needed to create nested layouts. So, another rule can use parent_layout_segment to refer parent layout (empty string is also ok).
  3. params — needed in case of dynamic slugs. Each round parentheses in pattern should be presented in params array, like: {pattern: /^\/(\d+)\/(\d+)\/?$/}, params: ['year', 'month']}
  4. You know, SvelteKit do not have only +page.svelte and +layout.svelte, but also:
  5. +page.js so { ... page_js: 'my_page.js' ... } could be passed in rule;
  6. +page.server.js —> page_server_js;
  7. +server.js —> server_js;
  8. +layout.js —> layout_js;
  9. +layout.server.js —> layout.server_js;
  10. Didn't notice any changes in playing with some depth param, but, just in case we can pass:
  11. layout_depth;
  12. page_depth;
  13. error_depth;
  14. depth (global); Just in case we'll find one day that it matters)

II. In svelte.config.js

import adapter from '@sveltejs/adapter-auto';
+ import {router} from './routes.js';  // <— add this

/** @type {import('@sveltejs/kit').Config} */
const config = {
+   routes: router(),  // <— add this
    kit: {
        adapter: adapter(),
export default config;
Enter fullscreen mode Exit fullscreen mode

III. In already familiar


1) Replace file system walking result with our code routes

292:    prevent_conflicts(routes);
+ 293:  routes.length = 0;
+ 294:  routes.push(...config.routes);
Enter fullscreen mode Exit fullscreen mode

2) We do not need any magic sorting any more

372:    return {
373:    nodes,
- 374:  routes: sort_routes(routes)
+ 375:  routes: routes
376:    };
Enter fullscreen mode Exit fullscreen mode

IV. Install patch-package, so this changes will be automatically applied in future without manual hacks:

> npm i patch-package
> npx patch-package @sveltejs/kit
Enter fullscreen mode Exit fullscreen mode


  "scripts": {
    "postinstall": "patch-package" // <— add this
Enter fullscreen mode Exit fullscreen mode

That's all! Easy :D

Top comments (2)

ktibow profile image
Kendell R

but why
also you sound a bit weird, are you a non-native speaker?

maxcore profile image
Max Core • Edited

Hi! At least because "me as a developer" (sounds like user story :D ), want to organise my code and name files and folders according to project's code-style etc., which was designed to serve "that current enterprise project" needs. So, let's say I just do not want framework to dictate how to name my files and folders, and how to respect its hierarchy. I need total control and flexibility (which only code can give).
And I know, that I am not the only one
So, just for them) I feel their pain too) Probably it would help someone

Ye, I am not native