git clone https://github.com/webentlib/router.gitTo your root folder (same level as package.json) to get router/ folder.
svelte.config.js:Add routes: 'router/' to config.kit.files:
const config = {
...
kit: {
files: {
...
routes: 'router/',
...
},
...
}
vite.config.js:Ensure allow: ['..'] in vite.config.ts, if not —
Add allow: ['..'] to server.fs:
export default defineConfig({
...
server: {
fs: {
allow: ['..'], // Allow serving files from one level up to the project root
},
},
}
all.tsOptionally, re-export Routers' vars via your root's all.ts.
At the very top of all.ts add:
// ROUTER
export * from '/router/';
export type * from '/router/';
urls.tsCreate urls.ts in the root folder (same level as package.json)
import type { Pattern, Layout, Error } from '/router/types'; // or from '/all.ts'
export const error: Error = () => import('/src/error.svelte');
const layout: Layout = { page: () => import('/src/base.svelte'), error };
const layouts: Layout[] = [layout]; // for nested layouts — [layout, sublayout, ...]
export const patterns: Pattern[] = [
{re: '', page: () => import('/src/home.svelte'), layouts},
]
Router expects
errorandpatternsto be exported.
error, layout and first route in pattern.svelte pages for 'error', 'base layout' and 'home', call them as you wanterror, layout and first route in patterns.Layout and PatternThere are 4 params, that are common for both 'layout' and 'pattern' declaration:
pageerrorjssideoptionsSo now we have only:
{re: '', page: () => import('/src/home.svelte'), layouts},
load function in home.sveltes' <script module>, just like it was in the good old Sapper.home.svelte:
<script module>
export async function load({ url, params, data, fetch, setHeaders, depends, parent, untrack }) {
const response = await fetch(`/api/articles/`);
const articles = await response.json();
return {articles}
}
</script>
load function in separate file, and specify its' path like this:{re: '', page: () => import('/src/home.svelte'), js: () => import('/src/home.js'), layouts},
load, add side param to pattern:import { Sides } from '/router/'; // or from '/all.ts' in case you use root index.ts
{re: '', page: () => import('/src/home.svelte'), page: () => import('/src/home.js'), side: Sides.SERVER, layouts}
There are 'SERVER', 'CLIENT' and 'UNIVERSAL'.
It could be passed via enum:
export enum Sides {
SERVER = 'SERVER',
CLIENT = 'CLIENT',
UNIVERSAL = 'UNIVERSAL',
}
Or just as string, like side: 'UNIVERSAL'.
Enum is recommended.
options param:https://svelte.dev/docs/kit/page-options
{re: 'articles/(<id>[0-9]+)', page: () => import('/src/articles/article.svelte'), layouts},
Here we declare group (<id>[0-9]+), where id is the named param, which is available in:
load function as in incoming param slugs objects.import { routeStore } from '/router/'; as $routeStore.slugs.So load function using slugs could look like that:
export async function load({ url, params, data, fetch, setHeaders, depends, parent, untrack, slugs }) {
const response = await fetch(`/api/articles/${slugs.id}/`);
const article = await response.json();
return { article }
}
Or using routeStore:
import { routeStore } from '/router/';
export async function load({ url, params, data, fetch, setHeaders, depends, parent, untrack, slugs }) {
const response = await fetch(`/api/articles/${slugs.id}/`);
const article = await response.json();
return {article}
}
If you inspect import type { Pattern } from '/router/types'; you'll see that there are much more options to pass to Pattern.
There are also:
layout?: string, // E.g: 'CUSTOM', 'CUSTOM'
wrapper?: string, // Can be used for css class for <main>
title?: string, // Can be used for <title>
h1?: string, // Can be used for <h1>
name?: string, // 'id' of a route
extras?: string[], // Any additions to use in layout
That's just variables, that could be defined in one place and used in base layout somehow.
They convert to properties of routeStore, and it's totally up to you how to use them, but here are an examples of a layout and page on steroids:
urls.ts:
import { Sides } from '/router/enums.ts';
import type { Pattern, Layout, Error } from '/router/types.ts';
// ENUMS
export enum Layouts {
DEFAULT = 'DEFAULT',
CUSTOM = 'CUSTOM',
BLANK = 'BLANK',
}
export enum Wrappers {
WIDE = 'WIDE',
DEFAULT = 'DEFAULT',
NARROW = 'NARROW',
}
export enum Extras {
GO_TOP = 'GO_TOP',
}
// ERRORS
export const error: Error = () => import('/src/error.svelte');
// DEFAULTS
const layout: Layout = {page: () => import('/src/base.svelte'), error: error}
const layouts: Layout[] = [layout];
// SPECIFIC
const task_layout: Layout = {page: () => import('/src/tasks/task.svelte'), error: error}
const task_layouts: Layout[] = [layout, task_layout];
export const patterns: Pattern[] = [
{
re: '',
page: () => import('/src/tasks/tasks.svelte'),
layouts: layouts,
layout: Layouts.DEFAULT,
extras: [Extras.GO_TOP],
wrapper: Wrappers.DEFAULT
},
{
re: 'articles',
page: () => import('/src/tasks/task_form.svelte'),
layouts: layouts,
layout: Layouts.DEFAULT,
extras: [],
wrapper: Wrappers.DEFAULT,
title: 'Articles',
h1: 'Recent Articles',
name: 'ARTICLES',
},
{
re: 'article/(<id>[0-9]+)',
page: () => import('/src/tasks/results.svelte'),
layouts: task_layouts,
layout: Layouts.CUSTOM,
extras: [Extras.GO_TOP],
wrapper: Wrappers.WIDE
},
]
base.svelte:
<script>
// ROUTER
import { routeStore, Layouts, Wrappers, Extras, beforeNavigate } from '/all.ts';
let title = $derived($routeStore.title);
let h1 = $derived($routeStore.h1);
let path = $derived($routeStore.url.pathname + $routeStore.url.search);
let layout = $derived($routeStore.layout);
let wrapper = $derived($routeStore.wrapper);
let name = $derived($routeStore.name);
let extras = $derived($routeStore.extras);
beforeNavigate(() => {
$titleStore = null;
$h1Store = null;
});
// CSRF
import '/lib/csrf.ts';
// CUSTOM
import { Nprogress, GoTop } from '/all.ts';
import Header from '/src/Header.svelte';
let { children } = $props();
</script>
<svelte:head>
<title>{title || 'My Project'}</title>
</svelte:head>
{#if layout !== Layouts.BLANK}
<Header/>
{/if}
{#key path}
{#if layout === Layouts.DEFAULT}
<main
class={`Wrapper`}
class:_Wide={wrapper === Wrappers.WIDE}
class:_Default={wrapper === Wrappers.DEFAULT}
class:_Narrow={wrapper === Wrappers.NARROW}
class:_Form={wrapper === Wrappers.FORM}
data-name={name}
>
{#if h1}
<div class="Heading">
<h1 class="Title">{h1}</h1>
</div>
{/if}
{@render children?.()}
</main>
{:else}
{@render children?.()}
{/if}
{/key}
<Nprogress/>
{#if extras?.length && extras.includes(Extras.GO_TOP)}
<GoTop/>
{/if}