Skip to content

Commit b2d8009

Browse files
committed
feat: added vike routegen as a default + more docs.
1 parent 6c6a350 commit b2d8009

9 files changed

Lines changed: 187 additions & 22 deletions

File tree

.vscode/default.code-snippets

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,8 @@
22
"New SolidJS Page": {
33
"prefix": "newsolidpage",
44
"scope": "javascriptreact,typescriptreact",
5-
"body": [
6-
"export default function ${1:MyAwesome}Page() {",
7-
" return <></>;",
8-
"}",
9-
],
10-
"description": "Creates a new page in SolidJS.",
5+
"body": ["export default function ${1:MyAwesome}Page() {", " return <></>;", "}"],
6+
"description": "Creates a new page in SolidJS."
117
},
128

139
"New SolidJS Component": {
@@ -23,9 +19,9 @@
2319
"",
2420
" return <></>;",
2521
"}",
26-
"",
22+
""
2723
],
28-
"description": "Creates a new SolidJS Component",
24+
"description": "Creates a new SolidJS Component"
2925
},
3026

3127
"New SolidJS Context File": {
@@ -36,7 +32,7 @@
3632
"import { createStrictContext } from '@/utils/create-strict-context';",
3733
"",
3834
"// ===========================================================================",
39-
"// Context",
35+
"// Context & Hook",
4036
"// ===========================================================================",
4137
"",
4238
"export type ${1:CounterContext}Value = {",
@@ -60,9 +56,9 @@
6056
" </${1:CounterContext}Provider>",
6157
" );",
6258
"};",
63-
"",
59+
""
6460
],
65-
"description": "New SolidJS Context File",
61+
"description": "New SolidJS Context File"
6662
},
6763

6864
"Comment Headers (TS)": {
@@ -71,9 +67,9 @@
7167
"body": [
7268
"// ===========================================================================",
7369
"// ${1:Comment Headers}",
74-
"// ===========================================================================",
70+
"// ==========================================================================="
7571
],
76-
"description": "Makes a comment header to subsection the content in your files.",
72+
"description": "Makes a comment header to subsection the content in your files."
7773
},
7874

7975
// Vike
@@ -92,8 +88,8 @@
9288
" return {",
9389
" todo: data as { userId: number; id: number; title: string; completed: boolean },",
9490
" };",
95-
"}",
91+
"}"
9692
],
97-
"description": "Vike +data.ts",
98-
},
93+
"description": "Vike +data.ts"
94+
}
9995
}

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ If you want a more opinionated and fully-featured boilerplate instead: http://gi
1515
3. Bun (Can swap this with Node easily if you want).
1616
4. Tools: Biome
1717

18+
## Features:
19+
20+
- [x] 🦋 **Type-safe Routing** - Inspired by TanStack Router, I'm the author of [`vike-routegen`](https://github.com/blankeos/vike-routegen) which codegens typesafe page routing for you, and it's a breeze!
21+
- [x] ⚡️ **Super-fast dev server** - way faster than NextJS thanks to Vite. You need to feel it to believe it! It can also literally build your app in seconds.
22+
- [x] **🥊 Robust Error Practices** - I thoughtfully made sure there's a best practice for errors here already. You can throw errors in a consistent manner in the backend and display them consistently in the frontend.
23+
1824
## Quick Start
1925

2026
1. Get template

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"lineWidth": 100
99
},
1010
"files": {
11-
"includes": ["**", "!dist/**/*", "!**/*.js", "!**/*.cjs", "!**/*.mjs"]
11+
"includes": ["**", "!dist/**/*", "!**/*.js", "!**/*.cjs", "!**/*.mjs", "!src/route-tree.gen.ts"]
1212
},
1313
"linter": {
1414
"enabled": true,

bun.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"author": "",
1818
"devDependencies": {
1919
"@biomejs/biome": "^2.3.2",
20+
"@blankeos/vike-routegen": "^0.0.3",
2021
"@types/bun": "^1.3.1",
2122
"typescript": "^5.9.3",
2223
"vite": "^7.1.12",

src/pages/+Layout.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createSignal, type FlowProps } from "solid-js"
22
import { useMetadata } from "vike-metadata-solid"
3+
import { getRoute } from "@/route-tree.gen"
34
import getTitle from "@/utils/get-title"
45

56
useMetadata.setGlobalDefaults({
@@ -12,9 +13,9 @@ export default function RootLayout(props: FlowProps) {
1213
<>
1314
<div>
1415
<nav>
15-
<a href="/">Home</a>
16+
<a href={getRoute("/")}>Home</a>
1617
<span>{" | "}</span>
17-
<a href="/dashboard">Dashboard</a>
18+
<a href={getRoute("/dashboard")}>Dashboard</a>
1819
<span>{" | "}</span>
1920
<Counter />
2021
</nav>

src/pages/dashboard/+Layout.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { createSignal, type FlowProps } from "solid-js"
2+
import { getRoute } from "@/route-tree.gen"
23

34
export default function DashboardLayout(props: FlowProps) {
45
return (
56
<div>
67
<aside>
7-
<a href="/dashboard">Dashboard</a>
8+
<a href={getRoute("/dashboard")}>Dashboard</a>
89
<span>{" | "}</span>
9-
<a href="/dashboard/settings">Settings</a>
10+
<a href={getRoute("/dashboard/settings")}>Settings</a>
1011
<span>{" | "}</span>
1112
<Counter />
1213
</aside>

src/route-tree.gen.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// /* eslint-disable */
2+
// @ts-nocheck
3+
4+
// noinspection JSUnusedGlobalSymbols
5+
6+
//// Automatically generated by Vike (routegen).
7+
//// - Used for typesafe routing.
8+
//// - DO NOT make any changes.
9+
//// - Make sure to exclude from linter/formatter.
10+
11+
export { pageRoutes, getRoute, useParams };
12+
export type { PageRoute, UseParamsResult };
13+
14+
const pageRoutes = [
15+
"/",
16+
"/dashboard",
17+
"/dashboard/settings"
18+
] as const;
19+
20+
type PageRoute = typeof pageRoutes[number];
21+
22+
/* For regular routes with named parameters. But it has a minor issue, it gets "" as a property, so this is prefixed with '_' */
23+
type _ExtractNamedParams<T extends string> = T extends `${string}@${infer Param}/${infer Rest}`
24+
? { [K in Param | keyof ExtractNamedParams<Rest>]: string }
25+
: T extends `${string}@${infer Param}`
26+
? { [K in Param]: string }
27+
: {};
28+
29+
/** Minor utility to prevent typescript from wrapping types. */
30+
type Prettify<T> = {
31+
[K in keyof T]: T[K];
32+
} & {};
33+
34+
/* For regular routes with named parameters */
35+
type ExtractNamedParams<T extends string> = Prettify<Omit<_ExtractNamedParams<T>, "">>;
36+
37+
/* Helper to determine if a route ends with a catch-all segment */
38+
type EndsWithCatchall<T extends string> = T extends `${string}/@` ? true : false;
39+
40+
/* Conditional type helper for determining if a route is a catchall/splat route */
41+
type IsCatchallRoute<T extends string> = EndsWithCatchall<T>;
42+
43+
/* Return type for UseParams */
44+
type UseParamsResult<T extends PageRoute> =
45+
IsCatchallRoute<T> extends true
46+
? ExtractNamedParams<T> & { '@': string[], '_@': string }
47+
: ExtractNamedParams<T>;
48+
49+
/* Conditional type helper for determining if a route has parameters */
50+
type HasParams<T extends string> =
51+
IsCatchallRoute<T> extends true
52+
? true
53+
: T extends `${string}@${string}`
54+
? true
55+
: false;
56+
57+
/* Type for the options of the getRoute function. */
58+
type GetRouteOptions<T extends PageRoute> = HasParams<T> extends true
59+
? IsCatchallRoute<T> extends true
60+
? {
61+
params: ExtractNamedParams<T> & { '@': string[] | string };
62+
search?: Record<string, string>;
63+
}
64+
: {
65+
params: ExtractNamedParams<T>;
66+
search?: Record<string, string>;
67+
}
68+
: {
69+
params?: never;
70+
search?: Record<string, string>;
71+
};
72+
73+
/* Typesafe helper to generate a route URL based on Vike pages folder. */
74+
function getRoute<T extends PageRoute>(
75+
route: T,
76+
...args: HasParams<T> extends true
77+
? [options: GetRouteOptions<T>]
78+
: [options?: GetRouteOptions<T>]
79+
): string {
80+
const options = args[0] || {};
81+
82+
// Handle both regular named parameters and catchall routes
83+
let result: string = route;
84+
85+
if (options.params) {
86+
// Handle named parameters first
87+
const params = { ...options.params };
88+
Object.entries(params).forEach(([key, value]) => {
89+
if (key !== '@') {
90+
result = result.replace(`@${key}`, String(value));
91+
}
92+
});
93+
94+
// Then handle catchall if present
95+
if (route.endsWith('/@') && '@' in params) {
96+
const catchallValue = params['@'];
97+
// Remove the trailing /@ from the result
98+
result = result.substring(0, result.length - 2);
99+
100+
if (Array.isArray(catchallValue)) {
101+
// If array, join with slashes
102+
result += `/${catchallValue.join('/')}`;
103+
} else if (typeof catchallValue === 'string') {
104+
// If string, add directly (with leading slash if needed)
105+
const prefix = catchallValue.startsWith('/') ? '' : '/';
106+
result += `${prefix}${catchallValue}`;
107+
}
108+
}
109+
}
110+
111+
if (options.search) {
112+
const searchParams = new URLSearchParams(options.search);
113+
result += `?${searchParams.toString()}`;
114+
}
115+
116+
return result;
117+
}
118+
119+
import { usePageContext } from 'vike-solid/usePageContext'
120+
import { createMemo } from 'solid-js'
121+
function useParams<T extends PageRoute>(params: { from: T }): () => UseParamsResult<T> {
122+
const pageContext = usePageContext();
123+
124+
return createMemo(() => {
125+
const routeParams = pageContext.routeParams as Record<string, string>;
126+
127+
// Check if this is a catch-all route
128+
if (params.from.endsWith("/@") && routeParams && "*" in routeParams) {
129+
const catchAllPath = routeParams["*"] as string;
130+
const segments = catchAllPath.split("/").filter(Boolean);
131+
132+
// Extract named parameters from the route
133+
const namedParams: Record<string, string> = {};
134+
const routeParts = params.from.split("/");
135+
const urlParts = pageContext.urlPathname.split("/");
136+
137+
for (let i = 0; i < routeParts.length - 1; i++) {
138+
const routePart = routeParts[i];
139+
if (routePart.startsWith("@")) {
140+
const paramName = routePart.slice(1);
141+
namedParams[paramName] = urlParts[i];
142+
}
143+
}
144+
145+
// Combine named parameters with catch-all segments
146+
return {
147+
...namedParams,
148+
"@": segments,
149+
"_@": catchAllPath.startsWith("/") ? catchAllPath : `/${catchAllPath}`,
150+
} as unknown as UseParamsResult<T>;
151+
}
152+
153+
// Handle regular dynamic routes without catch-all
154+
return routeParams as UseParamsResult<T>;
155+
});
156+
}

vite.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import vikeRoutegen from "@blankeos/vike-routegen"
12
import vike from "vike/plugin"
23
import vikeSolid from "vike-solid/vite"
34
import { defineConfig } from "vite"
45
import tsConfigPaths from "vite-tsconfig-paths"
56

67
export default defineConfig({
7-
plugins: [tsConfigPaths(), vike(), vikeSolid()],
8+
plugins: [tsConfigPaths(), vike(), vikeSolid(), vikeRoutegen()],
89
server: { port: 3000 },
910
preview: { port: 3000 },
1011
envPrefix: ["PUBLIC_"],

0 commit comments

Comments
 (0)