Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/router/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,10 @@
{
"label": "With tRPC",
"to": "framework/solid/examples/with-trpc"
},
{
"label": "Monorepo with Solid Query",
"to": "framework/solid/examples/router-monorepo-solid-query"
}
]
}
Expand Down
12 changes: 12 additions & 0 deletions examples/solid/router-monorepo-solid-query/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
*.js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Avoid overly broad *.js pattern.

Ignoring all .js files at the top level could unintentionally exclude source code or configuration files. Narrow this to specific directories (e.g., /dist/*.js or /packages/*/dist/) where compiled output lives, or remove it if the monorepo is TypeScript-only.

If this is a pure TypeScript monorepo with no committed .js source files, consider removing this line entirely. Otherwise, scope it to build output directories.


🏁 Script executed:

#!/bin/bash
# Retrieve the full .gitignore file
echo "=== Full .gitignore content ==="
cat examples/solid/router-monorepo-solid-query/.gitignore

echo -e "\n=== Project structure ==="
fd --type f -e json -e ts -e tsx examples/solid/router-monorepo-solid-query/ --max-depth 3 | head -20

echo -e "\n=== Check for package.json and tsconfig ==="
ls -la examples/solid/router-monorepo-solid-query/ | grep -E '(package\.json|tsconfig)'

echo -e "\n=== Look for build output directories ==="
fd --type d -e dist -e build -e out examples/solid/router-monorepo-solid-query/

Length of output: 1304


🏁 Script executed:

#!/bin/bash
# Check package.json to understand project configuration
echo "=== package.json content ==="
cat examples/solid/router-monorepo-solid-query/package.json

echo -e "\n=== Check for any .js source files (not in dist) ==="
fd --type f -name '*.js' examples/solid/router-monorepo-solid-query/ --exclude 'dist*' --exclude 'node_modules' 2>/dev/null | head -10 || echo "No .js source files found outside dist"

Length of output: 1156


Remove pnpm-workspace.yaml from .gitignore (critical for monorepo).

Workspace configuration files must be committed to version control so all developers and CI systems can access the monorepo structure. Line 12 should be removed entirely.

Additionally, line 6 (*.js) is acceptable for a TypeScript-only project but largely redundant since dist and dist-ssr are already explicitly ignored. Consider removing it unless you're also ignoring .js files in the root directory intentionally.

🤖 Prompt for AI Agents
In examples/solid/router-monorepo-solid-query/.gitignore around line 6 (and also
remove the entry at line 12), remove the pnpm-workspace.yaml entry so the
monorepo workspace config is committed, and delete the standalone "*.js" ignore
at line 6 unless you intentionally want to ignore .js files at the repo root; if
you do need to ignore built JS artifacts, prefer keeping explicit dist/ and
dist-ssr ignores and drop the general "*.js" rule.


/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
pnpm-workspace.yaml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove pnpm-workspace.yaml from .gitignore.

The workspace configuration file must be committed to the repository so that developers and CI systems can work with the monorepo structure. This should not be ignored.

Apply this diff:

- pnpm-workspace.yaml
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pnpm-workspace.yaml
🤖 Prompt for AI Agents
In examples/solid/router-monorepo-solid-query/.gitignore around line 12, the
workspace config file pnpm-workspace.yaml is currently ignored; remove that
entry from .gitignore and commit pnpm-workspace.yaml to the repo so the monorepo
workspace configuration is tracked and available to developers and CI systems.

3 changes: 3 additions & 0 deletions examples/solid/router-monorepo-solid-query/.stackblitzrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"startCommand": "cp pnpm-workspace.yaml.example pnpm-workspace.yaml && pnpm install && pnpm dev"
}
11 changes: 11 additions & 0 deletions examples/solid/router-monorepo-solid-query/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"files.watcherExclude": {
"**/routeTree.gen.ts": true
},
"search.exclude": {
"**/routeTree.gen.ts": true
},
"files.readonlyInclude": {
"**/routeTree.gen.ts": true
}
}
31 changes: 31 additions & 0 deletions examples/solid/router-monorepo-solid-query/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Example of a monorepo with router and feature libraries

To run this example:

- `npm install` or `yarn`
- `npm dev` or `yarn dev`

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.

This example showcases this approach using the following packages:

- `packages/router` is the router library
- `packages/post-query` is the post query collection library
- `packages/post-feature` is the posts ui library
- `packages/app` is the app

With this approach, we can use the query options from the data library both in the router and the feature library without creating circular dependencies.

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.

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!

Here is what it looks like in the monorepo:

![graph](./assets/graph.png)

## Stackblitz limitation

### TypeScript IDE feedback

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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions examples/solid/router-monorepo-solid-query/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "router-solid-monorepo-solid-query",
"version": "1.0.0",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"post-query": "pnpm --filter @router-solid-mono-solid-query/post-query",
"post-feature": "pnpm --filter @router-solid-mono-solid-query/post-feature",
"router": "pnpm --filter @router-solid-mono-solid-query/router",
"app": "pnpm --filter @router-solid-mono-solid-query/app",
"dev": "pnpm post-query build && pnpm router build && pnpm post-feature build && pnpm app dev"
},
"dependencies": {
"@tanstack/solid-query": "^5.90.0",
"@tanstack/solid-query-devtools": "^5.90.0",
"@tanstack/solid-router": "^1.135.2",
"@tanstack/solid-router-devtools": "^1.135.2",
"@tanstack/router-plugin": "^1.135.2",
"solid-js": "^1.9.10",
"redaxios": "^0.5.1"
},
"devDependencies": {
"@types/node": "^22.10.2",
"typescript": "^5.7.2",
"vite-plugin-solid": "^2.11.10",
"vite": "^7.1.7",
"vite-plugin-dts": "^4.5.4"
},
"keywords": [],
"author": "",
"license": "ISC"
}
12 changes: 12 additions & 0 deletions examples/solid/router-monorepo-solid-query/packages/app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "@router-solid-mono-solid-query/app",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --port=3001",
"build": "vite build && tsc --noEmit",
"serve": "vite preview",
"start": "vite"
},
"dependencies": {
"@tanstack/solid-query": "^5.90.0",
"@router-solid-mono-solid-query/post-feature": "workspace:*",
"@router-solid-mono-solid-query/router": "workspace:*",
"solid-js": "^1.9.10"
},
"devDependencies": {
"vite-plugin-solid": "^2.11.10",
"typescript": "^5.7.2",
"@tanstack/solid-router-devtools": "^1.135.2",
"postcss": "^8.5.1",
"autoprefixer": "^10.4.20",
"tailwindcss": "^3.4.17",
"vite": "^7.1.7",
"vite-plugin-dts": "^4.5.4"
},
"nx": {
"targets": {
"dev": {
"dependsOn": [
"^build"
]
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { render } from 'solid-js/web'
import { RouterProvider } from '@tanstack/solid-router'
import { QueryClientProvider } from '@tanstack/solid-query'
import { queryClient, router } from '@router-solid-mono-solid-query/router'
import {
PostErrorComponent,
PostIdComponent,
PostsListComponent,
} from '@router-solid-mono-solid-query/post-feature'
import { RootComponent } from './rootComponent'
import type { RouterIds } from '@router-solid-mono-solid-query/router'
import './style.css'
import type { JSX } from 'solid-js'
// Not lazy loaded for simplicity, but you could expose from your library component
// individually, and enforce here to use react lazy components via typings
// so that you have code splitting
Comment on lines +14 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update comment to reference Solid instead of React.

The comment mentions "react lazy components," but this is a Solid example.

Apply this diff:

-// Not lazy loaded for simplicity, but you could expose from your library component
-// individually, and enforce here to use react lazy components via typings
-// so that you have code splitting
+// Not lazy loaded for simplicity, but you could expose components from your library
+// individually, and enforce here to use Solid's lazy() components via typings
+// for code splitting
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Not lazy loaded for simplicity, but you could expose from your library component
// individually, and enforce here to use react lazy components via typings
// so that you have code splitting
// Not lazy loaded for simplicity, but you could expose components from your library
// individually, and enforce here to use Solid's lazy() components via typings
// for code splitting
🤖 Prompt for AI Agents
examples/solid/router-monorepo-solid-query/packages/app/src/main.tsx around
lines 14 to 16: update the inline comment to reference Solid instead of React;
replace "react lazy components" wording with "Solid lazy components" or "Solid's
lazy-loading" and adjust surrounding text to mention Solid-specific code
splitting (e.g., Solid's lazy helpers) so the comment accurately reflects this
Solid example.

const routerMap = {
'/': PostsListComponent,
'/$postId': PostIdComponent,
__root__: RootComponent,
} as const satisfies Record<RouterIds, () => JSX.Element>

Object.entries(routerMap).forEach(([path, component]) => {
const foundRoute = router.routesById[path as RouterIds]

foundRoute.update({
component: component,
})
})

// And you can do the same logic with custom error pages, and any other properties
const errorComponentMap = {
'/': null,
'/$postId': PostErrorComponent,
__root__: null,
}

Object.entries(errorComponentMap).forEach(([path, component]) => {
if (!component) {
return
}

const foundRoute = router.routesById[path as RouterIds]
foundRoute.update({
errorComponent: component,
})
})

const rootElement = document.getElementById('app')!

if (!rootElement.innerHTML) {
render(
() => (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
),
rootElement,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Link, Outlet } from '@router-solid-mono-solid-query/router'
import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools'
import { SolidQueryDevtools } from '@tanstack/solid-query-devtools'

export function RootComponent() {
return (
<>
<div class="p-2 flex gap-2 text-lg">
<Link
to="/"
activeProps={{
class: 'font-bold',
}}
activeOptions={{ exact: true }}
>
Posts list
</Link>
</div>
<hr />
<Outlet />
<SolidQueryDevtools buttonPosition="top-right" />
<TanStackRouterDevtools position="bottom-right" />
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

html {
color-scheme: light dark;
}
* {
@apply border-gray-200 dark:border-gray-800;
}
body {
@apply bg-gray-50 text-gray-950 dark:bg-gray-900 dark:text-gray-200;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"target": "ESNext",
"moduleResolution": "Bundler",
"module": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"skipLibCheck": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { fileURLToPath } from 'node:url'
import * as path from 'node:path'
import { defineConfig } from 'vite'
import dts from 'vite-plugin-dts'
import solid from 'vite-plugin-solid'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/packages/router',

server: {
port: 4200,
host: 'localhost',
},

preview: {
port: 4300,
host: 'localhost',
},
plugins: [
dts({
entryRoot: 'src',
tsconfigPath: path.join(__dirname, 'tsconfig.json'),
}),
solid(),
],
build: {
outDir: './dist',
emptyOutDir: true,
commonjsOptions: {
transformMixedEsModules: true,
},
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@router-solid-mono-solid-query/post-feature",
"private": true,
"type": "module",
"scripts": {
"build": "vite build && tsc --noEmit"
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"dependencies": {
"@tanstack/solid-query": "^5.90.0",
"@router-solid-mono-solid-query/post-query": "workspace:*",
"@router-solid-mono-solid-query/router": "workspace:*",
"solid-js": "^1.9.10"
},
"devDependencies": {
"vite-plugin-solid": "^2.11.10",
"typescript": "^5.7.2",
"vite": "^7.1.7",
"vite-plugin-dts": "^4.5.4"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// import { createEffect } from 'solid-js'
import { PostNotFoundError } from '@router-solid-mono-solid-query/post-query'
// import { useQueryErrorResetBoundary } from '@tanstack/solid-query'
Comment on lines +1 to +3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove commented-out imports.

Dead code should be removed to keep the codebase clean.

Apply this diff:

-// import { createEffect } from 'solid-js'
 import { PostNotFoundError } from '@router-solid-mono-solid-query/post-query'
-// import { useQueryErrorResetBoundary } from '@tanstack/solid-query'
 import {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// import { createEffect } from 'solid-js'
import { PostNotFoundError } from '@router-solid-mono-solid-query/post-query'
// import { useQueryErrorResetBoundary } from '@tanstack/solid-query'
import { PostNotFoundError } from '@router-solid-mono-solid-query/post-query'
🤖 Prompt for AI Agents
In
examples/solid/router-monorepo-solid-query/packages/post-feature/src/PostError.tsx
around lines 1 to 3, remove the commented-out import lines (// import {
createEffect } from 'solid-js' and // import { useQueryErrorResetBoundary } from
'@tanstack/solid-query') so the file only keeps the active import (import {
PostNotFoundError } from '@router-solid-mono-solid-query/post-query'); delete
those dead-code comment lines to clean up the file.

import {
ErrorComponent,
useRouter,
} from '@router-solid-mono-solid-query/router'
import type { ErrorComponentProps } from '@router-solid-mono-solid-query/router'

export function PostErrorComponent({ error, reset }: ErrorComponentProps) {
const router = useRouter()
if (error instanceof PostNotFoundError) {
return <div>{error.message}</div>
}

return (
<div>
<button
onClick={() => {
router.invalidate()
}}
>
retry
</button>
<ErrorComponent error={error} />
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useQuery } from '@tanstack/solid-query'
import { postQueryOptions } from '@router-solid-mono-solid-query/post-query'

import { getRouteApi } from '@router-solid-mono-solid-query/router'

const route = getRouteApi('/$postId')

export function PostIdComponent() {
const postId = route.useParams()().postId
const { data: post } = useQuery(() => postQueryOptions(postId))

return (
<div class="space-y-2">
<h4 class="text-xl font-bold underline">{post?.title}</h4>
<div class="text-sm">{post?.body}</div>
</div>
)
}
Loading
Loading