DEV Community

Cover image for Trying Out Type Safe Navigation in SvelteKit
qaynam
qaynam

Posted on • Edited on

3 1

Trying Out Type Safe Navigation in SvelteKit

Recently Switched from Next.js to SvelteKit and Loving It

Motivation

I wanted to implement tansack-router typing in Svelte like this👇

nextjs-typesafe-route

So I built my own and wanted to document it here.

Implementation

SvelteKit puts a lot of emphasis on type safety between server and client. When you start the dev server, it generates various types for data returned by load functions in .sveltekit/types.

You can import and use them from the $types alias like this 👇

load-type

https://kit.svelte.dev/docs/load#page-data

I thought I could do something similar with code generation. Looking through the SvelteKit source code, I found the create_manifest_data function




export default function create_manifest_data({
    config,
    fallback = `${runtime_directory}/components`,
    cwd = process.cwd()
}) {
    const assets = create_assets(config);
    const matchers = create_matchers(config, cwd);
    const { nodes, routes } = create_routes_and_nodes(cwd, config, fallback);

    for (const route of routes) {
        for (const param of route.params) {
            if (param.matcher && !matchers[param.matcher]) {
                throw new Error(`No matcher found for parameter '${param.matcher}' in route ${route.id}`);
            }
        }
    }

    return {
        assets,
        matchers,
        nodes,
        routes
    };
}


Enter fullscreen mode Exit fullscreen mode

github link

I created a codegen.js file in the project root. It runs node codegen.js when starting dev server to continually watch for file changes.




import create_manifest_data from './node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/index.js';
import { load_config } from './node_modules/@sveltejs/kit/src/core/config/index.js';
import fs from 'node:fs';
import { format } from 'prettier';

const specialRouteIdPattern = /(\/(\(|\[).*(\]|\)))?/g;

async function getRoutes() {
    /** @type import('@sveltejs/kit').ValidatedConfig */
    const config = await load_config();

    /** @type import("@sveltejs/kit").ManifestData */
    const manifest = await create_manifest_data({ config });
    return manifest.routes;
}

async function generateRouteTypeFile(
    /** @type import("@sveltejs/kit").RouteData[] */
    routes
) {
    const ids = routes
        .filter(
            (route) => Boolean(route.page))
        .map((route) => route.id.split('').join('').replace(specialRouteIdPattern, '')).filter(Boolean);
    const type = `export type RouteList = ${ids.map((id) => `"${id}"`).join(' | ')}`;

    fs.writeFileSync('./src/lib/type.d.ts', await format(type, { parser: 'typescript' }));

    return { routes: ids };
}

const { routes } = await generateRouteTypeFile(await getRoutes());
console.log('Routes:');
console.table(routes);
console.log('Generated route type file ✅');

console.log('Watching for changes...');
fs.watch('./src/routes', { recursive: true }, async (event, filename) => {
    console.log(`Change detected in ${filename} `);
    if (event === 'rename') {
        const routes = await getRoutes();
        await generateRouteTypeFile(routes);
        console.log('Generated route type file ✅');
    }
});




Enter fullscreen mode Exit fullscreen mode

To briefly explain the code, we use a module that is not publicly available, get a route, exclude specific routes such as [.... .path] (gourp), generate the ts code, write it to the file, and keep waiting for the file to change.

When dev server starts, it generates src/lib/type.d.ts with route ids like:




export type RouteList = "/" | "/auth" | "/auth/forgot-password" | "/auth/reset-password" | "/auth/sign-in" | "/auth/sign-up"



Enter fullscreen mode Exit fullscreen mode

Since SvelteKit has $app/navigation instead of react-router, I wrapped it in lib/navigation.ts file like this👇



import { goto } from '$app/navigation';

import type { RouteList } from '../routes/type';

export const navigate = (url: RouteList | URL, opt?: GotoParams[1]) => goto(url, opt);



Enter fullscreen mode Exit fullscreen mode

Now I get type hints like:

sample

Warning

This module is not exported from @sveltejs/kit, so it may stop working if destructive changes are made.

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (3)

Collapse
 
evertt profile image
Evert van Brussel

Did / will / would you ever turn this into an npm package?

Collapse
 
qaynam profile image
qaynam

I posted on svelte's discord server and got a reply that there is already a package called vite-plugin-kit-routes.
I haven't tried it myself yet, but it looks pretty good.

Collapse
 
evertt profile image
Evert van Brussel

You have got to be kidding me. I spent so many hours yesterday basically trying to write the exact same plugin. Thanks for sharing man. I love having more type-safety and auto-completion everywhere.

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs