The idea is simple — just create universal [...path] folder and proxy everything through it specifying patterns in some urls.ts.
So, we have to create minimal and universal +page.js and +page.server.js with load functions, find there 'pattern' in urls.ts by url.pathname we know in load, set some routeStore, containing desired page, and get it in +page.svelte.
It's kinda tricky, so we also need copy router.ts from here:
https://github.com/webentlib/gists/blob/main/router.ts
Further message — just a copy of router.md.
Please, let me know what do u think.
Please share if you use something similar.
Minimal Example:
urls.ts:
const layout = {page: () => import('/src/base.svelte')};
export const patterns = [
{path: '', page: () => import('/src/home.svelte'), layouts: [layout]},
{path: 'users', page: () => import('/src/users/users.svelte'), layouts: [layout]},
{path: 'users/(<id>[0-9]+)', page: () => import('/src/users/user.svelte'), layouts: [layout]},
]
users.svelte
<script module>
export async function load() {
return {
users: [...]
}
}
</script>
<script>
let { data } = $props();
...
</script>
Installation:
Download router.ts to some folder all external gists live, e.g.: /lab/.
Move
routes/folder wherever you like, create[...path]folder inside.Create 4 files:
[...path]/+page.ts:
import {Router} from '/lab/router.ts';
export async function load(params) {
return await Router.route(params)
}
[...path]/+page.server.ts:
import {Router} from '/lab/router.ts';
export async function load(params) {
return await Router.route(params, true)
}
[...path]/+page.svelte:
<script>
import { routeStore } from '/lab/router.ts';
const { data } = $props();
</script>
{#snippet draw(routeStore, index)}
{@const Layout = routeStore.layouts[index]}
{@const Page = routeStore.page}
{#if routeStore.layouts.length && index < routeStore.layouts.length}
<Layout {data}>
{@render draw(routeStore, index + 1)}
</Layout>
{:else}
<Page {data}/>
{/if}
{/snippet}
{@render draw($routeStore, 0)}
+error.svelte (note — must be in routes/, not [...path]):
<script lang="ts">
import { page } from '$app/state';
import { Router } from '/lab/router.ts';
</script>
{#await Router.error(page.url.pathname) then Error}
{#if !Error}
<h1>{page.error.message}</h1>
{:else}
<Error/>
{/if}
{/await}
- Create urls in root (same level with
package.json):urls.ts:
const layout = {page: () => import('/src/base.svelte')};
export const patterns = [
{path: '', page: () => import('/src/home.svelte'), layouts: [layout]},
// {path: 'users/(<id>[0-9]+)', page: () => import('/src/users/user.svelte'), layouts: [layout]},
]
- Create sample pages in
/src/:base.svelte:
<script>
let { children } = $props();
</script>
{@render children?.()}
home.svelte:
Hello, world!
- Point svelte to routes folder you want it to be in:
svelte.config.ts:
kit: {
...
files: {
routes: 'routes/',
},
}
- Allow vite look files in root:
vite.config.ts:
export default defineConfig({
...
server: {
fs: {
allow: ['..'], // Allow serving files from one level up to the project root
},
}
});
Extended Example:
urls.ts:
import type { Pattern, Layout, Error } from '/lab/router.ts';
const error: Error = {page: () => import('/src/error.svelte')};
const layout: Layout = {page: () => import('/src/base.svelte'), error: error};
const account: Layout = {page: () => import('/src/account.svelte')};
export const patterns: Pattern[] = [
{path: '', page: () => import('/src/home.svelte'), layouts: [layout], title: 'Home', h1: 'Welcome'},
{path: 'users', page: () => import('/src/users/users.svelte'), layouts: [layout], name: 'users'},
{path: 'users/(<id>[0-9]+)', page: () => import('/src/users/user.svelte'), layouts: [layout]},
{path: 'friends', page: () => import('/src/users/users.svelte'), layouts: [layout, account], name: 'friends'},
{path: 'settings', page: () => import('/src/users/account.svelte'), layouts: [layout, account]},
]
Yes. One can specify:
- Layout array for any page.
- For sure multiple patterns can point to same page like
usersandfriendsin example in case same template but different data. - Custom error for any page or layout.
error.svelte:
<script lang="ts">
import { page } from '$app/state';
</script>
<h1>{page.status}</h1>
<div>{page.error.message}</div>
-
PatternandLayouthasuniversalandserverproperties to point toloadfunction in separate file:
{path: '', universal: () => import('/src/home.server.js'), server: () => import('/src/home.js'), ...},
- Add any custom attributes like
title,h1,nameto be used later in layout/page.base.svelte:
<script>
import { routeStore } from '/lab/router.ts';
let { children } = $props();
</script>
{#if $routeStore.pattern.h1}
<h1>{$routeStore.pattern.h1}</h1>
{/if}
{@render children?.()}
If one prefer both server and universal to be in <script module>:
user.svelte:
<script module>
import { get } from 'svelte/store';
import { routeStore } from '/lab/router.ts';
export async function server() { // in <script module> could be named only server
const user_id = get(routeStore).slugs.id;
...
}
export async function universal() { // could be named load
const user_id = get(routeStore).slugs.id;
...
}
</script>
Downsides:
Both
+page.server.jsand+page.jsruns on every rote. No way to say 'call only+page.js'.export const snapshot = {...}not working.No pragmatic way to specify options like
export let ssr = true;probably one can do it like (not tested):
urls.ts:
{path: '', ..., options: { ssr: true }},
+page.server.ts:
import {Router} from '/lab/router.ts';
export let prerender = false;
export let entries = () => [];
export let ssr = true;
export let csr = true;
export let trailingSlash = 'never';
export let config = {};
export async function load(params) {
const pattern = Router.findPattern(params.url.pathname);
for (const [k, v] of Object.entries(pattern?.options || {})) {
eval(`${k} = ${v}`);
}
return await Router.route(params, true)
}
Same for +page.ts but return await Router.route(params, true) must be return await Router.route(params) there.
P.S.
That router is TS only, and was written with next tsconfig:
tsconfig.json:
"rewriteRelativeImportExtensions": false,
"allowImportingTsExtensions": true,
"paths": {
"/*": ["*"]
},
"noImplicitAny": false,
United States
NORTH AMERICA
Related News
UCP Variant Data: The #1 Reason Agent Checkouts Fail
7h ago
Amazon Employees Are 'Tokenmaxxing' Due To Pressure To Use AI Tools
21h ago
How Braze’s CTO is rethinking engineering for the agentic area
10h ago

Décryptage technique : Comment builder un téléchargeur de vidéos Reddit performant (DASH, HLS & WebAssembly)
17h ago
How AI Reduced Manual Driver Verification by 75% — Operations Case Study. Part 2
4h ago