Skip to content

Commit 1cc3801

Browse files
docs(solid-router): router-monorepo-simple example (#5852)
* docs(solid-router): router-monorepo-simple example * delete extra lockfile * ci: apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 606dd19 commit 1cc3801

33 files changed

+713
-6
lines changed

docs/router/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,10 @@
682682
"label": "With tRPC",
683683
"to": "framework/solid/examples/with-trpc"
684684
},
685+
{
686+
"label": "Monorepo basic",
687+
"to": "framework/solid/examples/router-monorepo-simple"
688+
},
685689
{
686690
"label": "Monorepo with Solid Query",
687691
"to": "framework/solid/examples/router-monorepo-solid-query"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
node_modules
2+
.DS_Store
3+
dist
4+
dist-ssr
5+
*.local
6+
*.js
7+
8+
/test-results/
9+
/playwright-report/
10+
/blob-report/
11+
/playwright/.cache/
12+
13+
pnpm-workspace.yaml
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"startCommand": "cp pnpm-workspace.yaml.example pnpm-workspace.yaml && pnpm install && pnpm dev"
3+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"files.watcherExclude": {
3+
"**/routeTree.gen.ts": true
4+
},
5+
"search.exclude": {
6+
"**/routeTree.gen.ts": true
7+
},
8+
"files.readonlyInclude": {
9+
"**/routeTree.gen.ts": true
10+
}
11+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Example of a monorepo with router and feature libraries
2+
3+
To run this example:
4+
5+
- `npm install` or `yarn`
6+
- `npm dev` or `yarn dev`
7+
8+
A challenge with TanStack Router in a monorepo setup is that it requires TypeScript type augmentations. However, if you set this up directly in the final app, the links inside the libraries won’t be type-safe. To solve this in a monorepo, you need a separate library just for the router, without any components, and then integrate it with the app.
9+
10+
This example showcases this approach using the following packages:
11+
12+
- `packages/router` is the router library
13+
- `packages/post-feature` is the posts UI library
14+
- `packages/app` is the app
15+
16+
With this approach, we can use loaders in the router and the feature library without creating circular dependencies.
17+
18+
Since the router library re-exports the router components, importing them in the feature library ensures they remain type-safe, as they’re linked to the TypeScript augmentations.
19+
20+
Finally, in the app, we can create a map of routes to components ([`packages/app/src/main.tsx`](./packages/app/src/main.tsx)), which ties the router to the components. **We could enforce lazy loading here, but it was left out for simplicity.** With this setup, we now have a fully type-safe router!
21+
22+
Here is what it looks like in the monorepo:
23+
24+
![graph](./assets/graph.png)
25+
26+
## Stackblitz limitation
27+
28+
### Typescript IDE feedback
29+
30+
Due to a limitation on Stackblitz, the example's types are not properly inferred in the IDE, however as soon as you click on fork in the bottom right corner, the types should be correctly inferred.
29.6 KB
Loading
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "router-solid-mono-simple",
3+
"version": "1.0.0",
4+
"scripts": {
5+
"post-feature": "pnpm --filter @router-solid-mono-simple/post-feature",
6+
"router": "pnpm --filter @router-solid-mono-simple/router",
7+
"app": "pnpm --filter @router-solid-mono-simple/app",
8+
"dev": "pnpm router build && pnpm post-feature build && pnpm app dev"
9+
},
10+
"dependencies": {
11+
"@tanstack/solid-router": "^1.135.2",
12+
"@tanstack/solid-router-devtools": "^1.135.2",
13+
"@tanstack/router-plugin": "^1.135.2",
14+
"solid-js": "^1.9.10",
15+
"redaxios": "^0.5.1"
16+
},
17+
"devDependencies": {
18+
"@types/node": "^22.7.4",
19+
"vite-plugin-solid": "^2.11.10",
20+
"typescript": "^5.7.2",
21+
"vite": "^7.1.7",
22+
"vite-plugin-dts": "^4.5.4"
23+
},
24+
"keywords": [],
25+
"author": "",
26+
"license": "ISC"
27+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Vite App</title>
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<script type="module" src="/src/main.tsx"></script>
11+
</body>
12+
</html>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "@router-solid-mono-simple/app",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "vite --port=3001",
7+
"build": "vite build && tsc --noEmit",
8+
"serve": "vite preview",
9+
"start": "vite"
10+
},
11+
"dependencies": {
12+
"@router-solid-mono-simple/post-feature": "workspace:*",
13+
"@router-solid-mono-simple/router": "workspace:*",
14+
"solid-js": "^1.9.10"
15+
},
16+
"devDependencies": {
17+
"vite-plugin-solid": "^2.11.10",
18+
"typescript": "^5.7.2",
19+
"@tanstack/solid-router-devtools": "^1.135.2",
20+
"vite": "^7.1.7",
21+
"postcss": "^8.5.1",
22+
"autoprefixer": "^10.4.20",
23+
"tailwindcss": "^3.4.17",
24+
"vite-plugin-dts": "^4.5.4"
25+
},
26+
"nx": {
27+
"targets": {
28+
"dev": {
29+
"dependsOn": [
30+
"^build"
31+
]
32+
}
33+
}
34+
}
35+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { render } from 'solid-js/web'
2+
import { RouterProvider } from '@tanstack/solid-router'
3+
import { Outlet, router } from '@router-solid-mono-simple/router'
4+
import {
5+
PostErrorComponent,
6+
PostIdComponent,
7+
PostsListComponent,
8+
} from '@router-solid-mono-simple/post-feature'
9+
import { RootComponent } from './rootComponent'
10+
import type { RouterIds } from '@router-solid-mono-simple/router'
11+
import type { JSX } from 'solid-js'
12+
import './style.css'
13+
// Not lazy loaded for simplicity, but you could expose from your library component
14+
// individually, and enforce here to use solid lazy components via typings
15+
// so that you have code splitting
16+
const routerMap = {
17+
'/': PostsListComponent,
18+
'/$postId': PostIdComponent,
19+
__root__: RootComponent,
20+
} as const satisfies Record<RouterIds, (() => JSX.Element) | null>
21+
22+
function EmptyComponent() {
23+
return <Outlet />
24+
}
25+
26+
Object.entries(routerMap).forEach(([path, component]) => {
27+
const foundRoute = router.routesById[path as RouterIds]
28+
foundRoute.update({
29+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
30+
component: component ?? EmptyComponent,
31+
})
32+
})
33+
34+
// And you can do the same logic with custom error pages, and any other properties
35+
const errorComponentMap = {
36+
'/': null,
37+
'/$postId': PostErrorComponent,
38+
__root__: null,
39+
}
40+
41+
Object.entries(errorComponentMap).forEach(([path, component]) => {
42+
if (!component) {
43+
return
44+
}
45+
46+
const foundRoute = router.routesById[path as RouterIds]
47+
foundRoute.update({
48+
errorComponent: component,
49+
})
50+
})
51+
52+
const rootElement = document.getElementById('app')!
53+
54+
if (!rootElement.innerHTML) {
55+
render(() => <RouterProvider router={router} />, rootElement)
56+
}

0 commit comments

Comments
 (0)