Trying Out Type Safe Navigation in SvelteKit
Tags: svelte, sveltekit, typescript, webdev
Published: 2024-01-11 13:25:28

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](https://storage.googleapis.com/zenn-user-upload/e95f1a2a20b7-20240111.gif)

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://storage.googleapis.com/zenn-user-upload/e3335e0fc611-20240111.png)

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

```js

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

[github link](https://github.com/sveltejs/kit/blob/d93eb42bee7fd06d4d3f7e3dde7381ab032b076c/packages/kit/src/core/sync/create_manifest_data/index.js#L19-L42)


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.

```js

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 ✅');
	}
});


```

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:

```ts

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

```

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


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

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

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

```

Now I get type hints like:

![sample](https://storage.googleapis.com/zenn-user-upload/9f25ef001f3b-20240111.png)

## Warning

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