diff --git a/e2e/solid-start/basic-auth/.env b/e2e/solid-start/basic-auth/.env
new file mode 100644
index 00000000000..c498ab59bf1
--- /dev/null
+++ b/e2e/solid-start/basic-auth/.env
@@ -0,0 +1,7 @@
+# Environment variables declared in this file are automatically made available to Prisma.
+# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
+
+# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
+# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
+
+DATABASE_URL="file:./dev.db"
\ No newline at end of file
diff --git a/e2e/solid-start/basic-auth/.gitignore b/e2e/solid-start/basic-auth/.gitignore
new file mode 100644
index 00000000000..75a469e80c0
--- /dev/null
+++ b/e2e/solid-start/basic-auth/.gitignore
@@ -0,0 +1,20 @@
+node_modules
+package-lock.json
+yarn.lock
+
+!.env
+.DS_Store
+.cache
+.vercel
+.output
+
+/build/
+/api/
+/server/build
+/public/build
+# Sentry Config File
+.env.sentry-build-plugin
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
diff --git a/e2e/solid-start/basic-auth/.prettierignore b/e2e/solid-start/basic-auth/.prettierignore
new file mode 100644
index 00000000000..2be5eaa6ece
--- /dev/null
+++ b/e2e/solid-start/basic-auth/.prettierignore
@@ -0,0 +1,4 @@
+**/build
+**/public
+pnpm-lock.yaml
+routeTree.gen.ts
\ No newline at end of file
diff --git a/e2e/solid-start/basic-auth/package.json b/e2e/solid-start/basic-auth/package.json
new file mode 100644
index 00000000000..07e9410205c
--- /dev/null
+++ b/e2e/solid-start/basic-auth/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "tanstack-solid-start-e2e-basic-auth",
+ "private": true,
+ "sideEffects": false,
+ "type": "module",
+ "scripts": {
+ "dev": "vite dev --port 3000",
+ "dev:e2e": "vite dev",
+ "build": "vite build",
+ "start": "node .output/server/index.mjs",
+ "prisma-generate": "prisma generate",
+ "test:e2e": "exit 0; rm -rf port*.txt; pnpm run prisma-generate && playwright test --project=chromium"
+ },
+ "dependencies": {
+ "@prisma/client": "5.22.0",
+ "@tanstack/solid-router": "workspace:^",
+ "@tanstack/solid-router-devtools": "workspace:^",
+ "@tanstack/solid-start": "workspace:^",
+ "prisma": "^5.22.0",
+ "solid-js": "^1.9.5",
+ "redaxios": "^0.5.1",
+ "tailwind-merge": "^2.6.0",
+ "vite": "^7.1.7"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.50.1",
+ "@tanstack/router-e2e-utils": "workspace:^",
+ "@types/node": "^22.10.2",
+ "vite-plugin-solid": "^2.11.9",
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.5.1",
+ "tailwindcss": "^3.4.17",
+ "typescript": "^5.7.2",
+ "vite-tsconfig-paths": "^5.1.4"
+ }
+}
diff --git a/e2e/solid-start/basic-auth/playwright.config.ts b/e2e/solid-start/basic-auth/playwright.config.ts
new file mode 100644
index 00000000000..18b8905468f
--- /dev/null
+++ b/e2e/solid-start/basic-auth/playwright.config.ts
@@ -0,0 +1,34 @@
+import { defineConfig, devices } from '@playwright/test'
+import { getTestServerPort } from '@tanstack/router-e2e-utils'
+import packageJson from './package.json' with { type: 'json' }
+
+const PORT = await getTestServerPort(packageJson.name)
+const baseURL = `http://localhost:${PORT}`
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+export default defineConfig({
+ testDir: './tests',
+ workers: 1,
+
+ reporter: [['line']],
+
+ use: {
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ baseURL,
+ },
+
+ webServer: {
+ command: `VITE_SERVER_PORT=${PORT} pnpm build && PORT=${PORT} VITE_SERVER_PORT=${PORT} pnpm start`,
+ url: baseURL,
+ reuseExistingServer: !process.env.CI,
+ stdout: 'pipe',
+ },
+
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+ ],
+})
diff --git a/e2e/solid-start/basic-auth/postcss.config.mjs b/e2e/solid-start/basic-auth/postcss.config.mjs
new file mode 100644
index 00000000000..2e7af2b7f1a
--- /dev/null
+++ b/e2e/solid-start/basic-auth/postcss.config.mjs
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/e2e/solid-start/basic-auth/prisma/dev.db b/e2e/solid-start/basic-auth/prisma/dev.db
new file mode 100644
index 00000000000..5f4ab51a029
Binary files /dev/null and b/e2e/solid-start/basic-auth/prisma/dev.db differ
diff --git a/e2e/solid-start/basic-auth/prisma/migrations/20240811183753_init/migration.sql b/e2e/solid-start/basic-auth/prisma/migrations/20240811183753_init/migration.sql
new file mode 100644
index 00000000000..4512a8f7823
--- /dev/null
+++ b/e2e/solid-start/basic-auth/prisma/migrations/20240811183753_init/migration.sql
@@ -0,0 +1,8 @@
+-- CreateTable
+CREATE TABLE "User" (
+ "email" TEXT NOT NULL PRIMARY KEY,
+ "password" TEXT NOT NULL
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
diff --git a/e2e/solid-start/basic-auth/prisma/migrations/migration_lock.toml b/e2e/solid-start/basic-auth/prisma/migrations/migration_lock.toml
new file mode 100644
index 00000000000..e5e5c4705ab
--- /dev/null
+++ b/e2e/solid-start/basic-auth/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "sqlite"
\ No newline at end of file
diff --git a/e2e/solid-start/basic-auth/prisma/schema.prisma b/e2e/solid-start/basic-auth/prisma/schema.prisma
new file mode 100644
index 00000000000..3544834310f
--- /dev/null
+++ b/e2e/solid-start/basic-auth/prisma/schema.prisma
@@ -0,0 +1,16 @@
+// This is your Prisma schema file,
+// learn more about it in the docs: https://pris.ly/d/prisma-schema
+
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "sqlite"
+ url = env("DATABASE_URL")
+}
+
+model User {
+ email String @id @unique
+ password String
+}
\ No newline at end of file
diff --git a/e2e/solid-start/basic-auth/public/android-chrome-192x192.png b/e2e/solid-start/basic-auth/public/android-chrome-192x192.png
new file mode 100644
index 00000000000..09c8324f8c6
Binary files /dev/null and b/e2e/solid-start/basic-auth/public/android-chrome-192x192.png differ
diff --git a/e2e/solid-start/basic-auth/public/android-chrome-512x512.png b/e2e/solid-start/basic-auth/public/android-chrome-512x512.png
new file mode 100644
index 00000000000..11d626ea3d0
Binary files /dev/null and b/e2e/solid-start/basic-auth/public/android-chrome-512x512.png differ
diff --git a/e2e/solid-start/basic-auth/public/apple-touch-icon.png b/e2e/solid-start/basic-auth/public/apple-touch-icon.png
new file mode 100644
index 00000000000..5a9423cc02c
Binary files /dev/null and b/e2e/solid-start/basic-auth/public/apple-touch-icon.png differ
diff --git a/e2e/solid-start/basic-auth/public/favicon-16x16.png b/e2e/solid-start/basic-auth/public/favicon-16x16.png
new file mode 100644
index 00000000000..e3389b00443
Binary files /dev/null and b/e2e/solid-start/basic-auth/public/favicon-16x16.png differ
diff --git a/e2e/solid-start/basic-auth/public/favicon-32x32.png b/e2e/solid-start/basic-auth/public/favicon-32x32.png
new file mode 100644
index 00000000000..900c77d444c
Binary files /dev/null and b/e2e/solid-start/basic-auth/public/favicon-32x32.png differ
diff --git a/e2e/solid-start/basic-auth/public/favicon.ico b/e2e/solid-start/basic-auth/public/favicon.ico
new file mode 100644
index 00000000000..1a1751676f7
Binary files /dev/null and b/e2e/solid-start/basic-auth/public/favicon.ico differ
diff --git a/e2e/solid-start/basic-auth/public/favicon.png b/e2e/solid-start/basic-auth/public/favicon.png
new file mode 100644
index 00000000000..1e77bc06091
Binary files /dev/null and b/e2e/solid-start/basic-auth/public/favicon.png differ
diff --git a/e2e/solid-start/basic-auth/public/site.webmanifest b/e2e/solid-start/basic-auth/public/site.webmanifest
new file mode 100644
index 00000000000..fa99de77db6
--- /dev/null
+++ b/e2e/solid-start/basic-auth/public/site.webmanifest
@@ -0,0 +1,19 @@
+{
+ "name": "",
+ "short_name": "",
+ "icons": [
+ {
+ "src": "/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
diff --git a/e2e/solid-start/basic-auth/src/components/Auth.tsx b/e2e/solid-start/basic-auth/src/components/Auth.tsx
new file mode 100644
index 00000000000..40e44b8c5a4
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/components/Auth.tsx
@@ -0,0 +1,59 @@
+import type { JSX } from 'solid-js'
+
+export function Auth({
+ actionText,
+ onSubmit,
+ status,
+ afterSubmit,
+}: {
+ actionText: string
+ onSubmit: (e: HTMLFormElement) => void
+ status: 'pending' | 'idle' | 'success' | 'error'
+ afterSubmit?: JSX.Element
+}) {
+ return (
+
+ )
+}
diff --git a/e2e/solid-start/basic-auth/src/components/DefaultCatchBoundary.tsx b/e2e/solid-start/basic-auth/src/components/DefaultCatchBoundary.tsx
new file mode 100644
index 00000000000..32aed20e675
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/components/DefaultCatchBoundary.tsx
@@ -0,0 +1,53 @@
+import {
+ ErrorComponent,
+ Link,
+ rootRouteId,
+ useMatch,
+ useRouter,
+} from '@tanstack/solid-router'
+import type { ErrorComponentProps } from '@tanstack/solid-router'
+
+export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
+ const router = useRouter()
+ const isRoot = useMatch({
+ strict: false,
+ select: (state) => state.id === rootRouteId,
+ })
+
+ console.error(error)
+
+ return (
+
+
+
+
+ {isRoot() ? (
+
+ Home
+
+ ) : (
+ {
+ e.preventDefault()
+ window.history.back()
+ }}
+ >
+ Go Back
+
+ )}
+
+
+ )
+}
diff --git a/e2e/solid-start/basic-auth/src/components/Login.tsx b/e2e/solid-start/basic-auth/src/components/Login.tsx
new file mode 100644
index 00000000000..328149bf318
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/components/Login.tsx
@@ -0,0 +1,71 @@
+import { useRouter } from '@tanstack/solid-router'
+import { useServerFn } from '@tanstack/solid-start'
+import { useMutation } from '../hooks/useMutation'
+import { loginFn } from '../routes/_authed'
+import { Auth } from './Auth'
+import { signupFn } from '~/routes/signup'
+
+export function Login() {
+ const router = useRouter()
+
+ const loginMutation = useMutation({
+ fn: loginFn,
+ onSuccess: async (ctx) => {
+ if (!ctx.data?.error) {
+ await router.invalidate()
+ router.navigate({ to: '/' })
+ return
+ }
+ },
+ })
+
+ const signupMutation = useMutation({
+ fn: useServerFn(signupFn),
+ })
+
+ return (
+ {
+ const formData = new FormData(e.target as unknown as HTMLFormElement)
+
+ loginMutation.mutate({
+ data: {
+ email: formData.get('email') as string,
+ password: formData.get('password') as string,
+ },
+ })
+ }}
+ afterSubmit={
+ loginMutation.data() ? (
+ <>
+ {loginMutation.data()?.message}
+ {loginMutation.data()?.userNotFound ? (
+
+
+
+ ) : null}
+ >
+ ) : null
+ }
+ />
+ )
+}
diff --git a/e2e/solid-start/basic-auth/src/components/NotFound.tsx b/e2e/solid-start/basic-auth/src/components/NotFound.tsx
new file mode 100644
index 00000000000..ca4c1960fa1
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/components/NotFound.tsx
@@ -0,0 +1,25 @@
+import { Link } from '@tanstack/solid-router'
+
+export function NotFound({ children }: { children?: any }) {
+ return (
+
+
+ {children ||
The page you are looking for does not exist.
}
+
+
+
+
+ Start Over
+
+
+
+ )
+}
diff --git a/e2e/solid-start/basic-auth/src/hooks/useMutation.ts b/e2e/solid-start/basic-auth/src/hooks/useMutation.ts
new file mode 100644
index 00000000000..102ecf87fc8
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/hooks/useMutation.ts
@@ -0,0 +1,41 @@
+import * as Solid from 'solid-js'
+
+export function useMutation(opts: {
+ fn: (variables: TVariables) => Promise
+ onSuccess?: (ctx: { data: TData }) => void | Promise
+}) {
+ const [submittedAt, setSubmittedAt] = Solid.createSignal()
+ const [variables, setVariables] = Solid.createSignal()
+ const [error, setError] = Solid.createSignal()
+ const [data, setData] = Solid.createSignal()
+ const [status, setStatus] = Solid.createSignal<
+ 'idle' | 'pending' | 'success' | 'error'
+ >('idle')
+
+ const mutate = async (variables: TVariables): Promise => {
+ setStatus('pending')
+ setSubmittedAt(Date.now())
+ setVariables(variables as any)
+ //
+ try {
+ const data = await opts.fn(variables)
+ await opts.onSuccess?.({ data })
+ setStatus('success')
+ setError(undefined)
+ setData(data as any)
+ return data
+ } catch (err: any) {
+ setStatus('error')
+ setError(err)
+ }
+ }
+
+ return {
+ status,
+ variables,
+ submittedAt,
+ mutate,
+ error,
+ data,
+ }
+}
diff --git a/e2e/solid-start/basic-auth/src/routeTree.gen.ts b/e2e/solid-start/basic-auth/src/routeTree.gen.ts
new file mode 100644
index 00000000000..86521aa7894
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/routeTree.gen.ts
@@ -0,0 +1,225 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as SignupRouteImport } from './routes/signup'
+import { Route as LogoutRouteImport } from './routes/logout'
+import { Route as LoginRouteImport } from './routes/login'
+import { Route as AuthedRouteImport } from './routes/_authed'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as AuthedPostsRouteImport } from './routes/_authed/posts'
+import { Route as AuthedPostsIndexRouteImport } from './routes/_authed/posts.index'
+import { Route as AuthedPostsPostIdRouteImport } from './routes/_authed/posts.$postId'
+
+const SignupRoute = SignupRouteImport.update({
+ id: '/signup',
+ path: '/signup',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const LogoutRoute = LogoutRouteImport.update({
+ id: '/logout',
+ path: '/logout',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const LoginRoute = LoginRouteImport.update({
+ id: '/login',
+ path: '/login',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const AuthedRoute = AuthedRouteImport.update({
+ id: '/_authed',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const AuthedPostsRoute = AuthedPostsRouteImport.update({
+ id: '/posts',
+ path: '/posts',
+ getParentRoute: () => AuthedRoute,
+} as any)
+const AuthedPostsIndexRoute = AuthedPostsIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => AuthedPostsRoute,
+} as any)
+const AuthedPostsPostIdRoute = AuthedPostsPostIdRouteImport.update({
+ id: '/$postId',
+ path: '/$postId',
+ getParentRoute: () => AuthedPostsRoute,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/login': typeof LoginRoute
+ '/logout': typeof LogoutRoute
+ '/signup': typeof SignupRoute
+ '/posts': typeof AuthedPostsRouteWithChildren
+ '/posts/$postId': typeof AuthedPostsPostIdRoute
+ '/posts/': typeof AuthedPostsIndexRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/login': typeof LoginRoute
+ '/logout': typeof LogoutRoute
+ '/signup': typeof SignupRoute
+ '/posts/$postId': typeof AuthedPostsPostIdRoute
+ '/posts': typeof AuthedPostsIndexRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/_authed': typeof AuthedRouteWithChildren
+ '/login': typeof LoginRoute
+ '/logout': typeof LogoutRoute
+ '/signup': typeof SignupRoute
+ '/_authed/posts': typeof AuthedPostsRouteWithChildren
+ '/_authed/posts/$postId': typeof AuthedPostsPostIdRoute
+ '/_authed/posts/': typeof AuthedPostsIndexRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths:
+ | '/'
+ | '/login'
+ | '/logout'
+ | '/signup'
+ | '/posts'
+ | '/posts/$postId'
+ | '/posts/'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/login' | '/logout' | '/signup' | '/posts/$postId' | '/posts'
+ id:
+ | '__root__'
+ | '/'
+ | '/_authed'
+ | '/login'
+ | '/logout'
+ | '/signup'
+ | '/_authed/posts'
+ | '/_authed/posts/$postId'
+ | '/_authed/posts/'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ AuthedRoute: typeof AuthedRouteWithChildren
+ LoginRoute: typeof LoginRoute
+ LogoutRoute: typeof LogoutRoute
+ SignupRoute: typeof SignupRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/signup': {
+ id: '/signup'
+ path: '/signup'
+ fullPath: '/signup'
+ preLoaderRoute: typeof SignupRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/logout': {
+ id: '/logout'
+ path: '/logout'
+ fullPath: '/logout'
+ preLoaderRoute: typeof LogoutRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/login': {
+ id: '/login'
+ path: '/login'
+ fullPath: '/login'
+ preLoaderRoute: typeof LoginRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/_authed': {
+ id: '/_authed'
+ path: ''
+ fullPath: ''
+ preLoaderRoute: typeof AuthedRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/_authed/posts': {
+ id: '/_authed/posts'
+ path: '/posts'
+ fullPath: '/posts'
+ preLoaderRoute: typeof AuthedPostsRouteImport
+ parentRoute: typeof AuthedRoute
+ }
+ '/_authed/posts/': {
+ id: '/_authed/posts/'
+ path: '/'
+ fullPath: '/posts/'
+ preLoaderRoute: typeof AuthedPostsIndexRouteImport
+ parentRoute: typeof AuthedPostsRoute
+ }
+ '/_authed/posts/$postId': {
+ id: '/_authed/posts/$postId'
+ path: '/$postId'
+ fullPath: '/posts/$postId'
+ preLoaderRoute: typeof AuthedPostsPostIdRouteImport
+ parentRoute: typeof AuthedPostsRoute
+ }
+ }
+}
+
+interface AuthedPostsRouteChildren {
+ AuthedPostsPostIdRoute: typeof AuthedPostsPostIdRoute
+ AuthedPostsIndexRoute: typeof AuthedPostsIndexRoute
+}
+
+const AuthedPostsRouteChildren: AuthedPostsRouteChildren = {
+ AuthedPostsPostIdRoute: AuthedPostsPostIdRoute,
+ AuthedPostsIndexRoute: AuthedPostsIndexRoute,
+}
+
+const AuthedPostsRouteWithChildren = AuthedPostsRoute._addFileChildren(
+ AuthedPostsRouteChildren,
+)
+
+interface AuthedRouteChildren {
+ AuthedPostsRoute: typeof AuthedPostsRouteWithChildren
+}
+
+const AuthedRouteChildren: AuthedRouteChildren = {
+ AuthedPostsRoute: AuthedPostsRouteWithChildren,
+}
+
+const AuthedRouteWithChildren =
+ AuthedRoute._addFileChildren(AuthedRouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ AuthedRoute: AuthedRouteWithChildren,
+ LoginRoute: LoginRoute,
+ LogoutRoute: LogoutRoute,
+ SignupRoute: SignupRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/solid-start'
+declare module '@tanstack/solid-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/e2e/solid-start/basic-auth/src/router.tsx b/e2e/solid-start/basic-auth/src/router.tsx
new file mode 100644
index 00000000000..6b397aa78d6
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/router.tsx
@@ -0,0 +1,16 @@
+import { createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+import { DefaultCatchBoundary } from './components/DefaultCatchBoundary'
+import { NotFound } from './components/NotFound'
+
+export function getRouter() {
+ const router = createRouter({
+ routeTree,
+ scrollRestoration: true,
+ defaultPreload: 'intent',
+ defaultErrorComponent: DefaultCatchBoundary,
+ defaultNotFoundComponent: () => ,
+ })
+
+ return router
+}
diff --git a/e2e/solid-start/basic-auth/src/routes/__root.tsx b/e2e/solid-start/basic-auth/src/routes/__root.tsx
new file mode 100644
index 00000000000..3a3392e9223
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/routes/__root.tsx
@@ -0,0 +1,144 @@
+///
+import {
+ HeadContent,
+ Link,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/solid-router'
+import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools'
+import { createServerFn } from '@tanstack/solid-start'
+
+import type * as Solid from 'solid-js'
+import { HydrationScript } from 'solid-js/web'
+import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary.js'
+import { NotFound } from '~/components/NotFound.js'
+import appCss from '~/styles/app.css?url'
+import { seo } from '~/utils/seo.js'
+import { useAppSession } from '~/utils/session.js'
+
+const fetchUser = createServerFn({ method: 'GET' }).handler(async () => {
+ // We need to auth on the server so we have access to secure cookies
+ const session = await useAppSession()
+
+ if (!session.data.userEmail) {
+ return null
+ }
+
+ return {
+ email: session.data.userEmail,
+ }
+})
+
+export const Route = createRootRoute({
+ beforeLoad: async () => {
+ const user = await fetchUser()
+
+ return {
+ user,
+ }
+ },
+ head: () => ({
+ meta: [
+ {
+ charset: 'utf-8',
+ },
+ {
+ name: 'viewport',
+ content: 'width=device-width, initial-scale=1',
+ },
+ ...seo({
+ title:
+ 'TanStack Start | Type-Safe, Client-First, Full-Stack React Framework',
+ description: `TanStack Start is a type-safe, client-first, full-stack React framework. `,
+ }),
+ ],
+ links: [
+ { rel: 'stylesheet', href: appCss },
+ {
+ rel: 'apple-touch-icon',
+ sizes: '180x180',
+ href: '/apple-touch-icon.png',
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '32x32',
+ href: '/favicon-32x32.png',
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '16x16',
+ href: '/favicon-16x16.png',
+ },
+ { rel: 'manifest', href: '/site.webmanifest', color: '#fffff' },
+ { rel: 'icon', href: '/favicon.ico' },
+ ],
+ }),
+ errorComponent: (props) => {
+ return (
+
+
+
+ )
+ },
+ notFoundComponent: () => ,
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+ )
+}
+
+function RootDocument({ children }: { children: Solid.JSX.Element }) {
+ const routeContext = Route.useRouteContext()
+
+ return (
+
+
+
+
+
+
+
+
+ Home
+ {' '}
+
+ Posts
+
+
+ {routeContext().user ? (
+ <>
+ {routeContext().user?.email}
+ Logout
+ >
+ ) : (
+ Login
+ )}
+
+
+
+ {children}
+
+
+
+
+ )
+}
diff --git a/e2e/solid-start/basic-auth/src/routes/_authed.tsx b/e2e/solid-start/basic-auth/src/routes/_authed.tsx
new file mode 100644
index 00000000000..612d8cfec77
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/routes/_authed.tsx
@@ -0,0 +1,61 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { createServerFn } from '@tanstack/solid-start'
+
+import { hashPassword, prismaClient } from '~/utils/prisma'
+import { Login } from '~/components/Login'
+import { useAppSession } from '~/utils/session'
+
+export const loginFn = createServerFn({
+ method: 'POST',
+})
+ .inputValidator((payload: { email: string; password: string }) => payload)
+ .handler(async ({ data }) => {
+ // Find the user
+ const user = await prismaClient.user.findUnique({
+ where: {
+ email: data.email,
+ },
+ })
+
+ // Check if the user exists
+ if (!user) {
+ return {
+ error: true,
+ userNotFound: true,
+ message: 'User not found',
+ }
+ }
+
+ // Check if the password is correct
+ const hashedPassword = await hashPassword(data.password)
+
+ if (user.password !== hashedPassword) {
+ return {
+ error: true,
+ message: 'Incorrect password',
+ }
+ }
+
+ // Create a session
+ const session = await useAppSession()
+
+ // Store the user's email in the session
+ await session.update({
+ userEmail: user.email,
+ })
+ })
+
+export const Route = createFileRoute('/_authed')({
+ beforeLoad: ({ context }) => {
+ if (!context.user) {
+ throw new Error('Not authenticated')
+ }
+ },
+ errorComponent: ({ error }) => {
+ if (error.message === 'Not authenticated') {
+ return
+ }
+
+ throw error
+ },
+})
diff --git a/e2e/solid-start/basic-auth/src/routes/_authed/posts.$postId.tsx b/e2e/solid-start/basic-auth/src/routes/_authed/posts.$postId.tsx
new file mode 100644
index 00000000000..602dca9c090
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/routes/_authed/posts.$postId.tsx
@@ -0,0 +1,29 @@
+import { ErrorComponent, createFileRoute } from '@tanstack/solid-router'
+import type { ErrorComponentProps } from '@tanstack/solid-router'
+
+import { NotFound } from '~/components/NotFound.js'
+import { fetchPost } from '~/utils/posts.js'
+
+export function PostErrorComponent({ error }: ErrorComponentProps) {
+ return
+}
+
+export const Route = createFileRoute('/_authed/posts/$postId')({
+ loader: ({ params: { postId } }) => fetchPost({ data: postId }),
+ errorComponent: PostErrorComponent,
+ component: PostComponent,
+ notFoundComponent: () => {
+ return Post not found
+ },
+})
+
+function PostComponent() {
+ const post = Route.useLoaderData()
+
+ return (
+
+
{post().title}
+
{post().body}
+
+ )
+}
diff --git a/e2e/solid-start/basic-auth/src/routes/_authed/posts.index.tsx b/e2e/solid-start/basic-auth/src/routes/_authed/posts.index.tsx
new file mode 100644
index 00000000000..56ac79f2aef
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/routes/_authed/posts.index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+function PostsIndexComponent() {
+ return Select a post.
+}
+
+export const Route = createFileRoute('/_authed/posts/')({
+ component: PostsIndexComponent,
+})
diff --git a/e2e/solid-start/basic-auth/src/routes/_authed/posts.tsx b/e2e/solid-start/basic-auth/src/routes/_authed/posts.tsx
new file mode 100644
index 00000000000..adc52137daf
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/routes/_authed/posts.tsx
@@ -0,0 +1,39 @@
+import { Link, Outlet, createFileRoute } from '@tanstack/solid-router'
+
+import { fetchPosts } from '~/utils/posts.js'
+
+function PostsComponent() {
+ const posts = Route.useLoaderData()
+
+ return (
+
+ )
+}
+
+export const Route = createFileRoute('/_authed/posts')({
+ loader: () => fetchPosts(),
+ component: PostsComponent,
+})
diff --git a/e2e/solid-start/basic-auth/src/routes/index.tsx b/e2e/solid-start/basic-auth/src/routes/index.tsx
new file mode 100644
index 00000000000..a128aeca0e1
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/routes/index.tsx
@@ -0,0 +1,13 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/')({
+ component: Home,
+})
+
+function Home() {
+ return (
+
+
Welcome Home!!!
+
+ )
+}
diff --git a/e2e/solid-start/basic-auth/src/routes/login.tsx b/e2e/solid-start/basic-auth/src/routes/login.tsx
new file mode 100644
index 00000000000..90a75e6867c
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/routes/login.tsx
@@ -0,0 +1,10 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { Login } from '~/components/Login'
+
+export const Route = createFileRoute('/login')({
+ component: LoginComp,
+})
+
+function LoginComp() {
+ return
+}
diff --git a/e2e/solid-start/basic-auth/src/routes/logout.tsx b/e2e/solid-start/basic-auth/src/routes/logout.tsx
new file mode 100644
index 00000000000..85b1670a5f0
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/routes/logout.tsx
@@ -0,0 +1,19 @@
+import { redirect, createFileRoute } from '@tanstack/solid-router'
+import { createServerFn } from '@tanstack/solid-start'
+
+import { useAppSession } from '~/utils/session'
+
+const logoutFn = createServerFn({ method: 'POST' }).handler(async () => {
+ const session = await useAppSession()
+
+ session.clear()
+
+ throw redirect({
+ href: '/',
+ })
+})
+
+export const Route = createFileRoute('/logout')({
+ preload: false,
+ loader: () => logoutFn(),
+})
diff --git a/e2e/solid-start/basic-auth/src/routes/signup.tsx b/e2e/solid-start/basic-auth/src/routes/signup.tsx
new file mode 100644
index 00000000000..48ee2981f5e
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/routes/signup.tsx
@@ -0,0 +1,100 @@
+import { createFileRoute, redirect } from '@tanstack/solid-router'
+import { createServerFn, useServerFn } from '@tanstack/solid-start'
+
+import { hashPassword, prismaClient } from '~/utils/prisma'
+import { useMutation } from '~/hooks/useMutation'
+import { Auth } from '~/components/Auth'
+import { useAppSession } from '~/utils/session'
+
+export const signupFn = createServerFn({
+ method: 'POST',
+})
+ .inputValidator(
+ (data: { email: string; password: string; redirectUrl?: string }) => data,
+ )
+ .handler(async ({ data: payload }) => {
+ // Check if the user already exists
+ const found = await prismaClient.user.findUnique({
+ where: {
+ email: payload.email,
+ },
+ })
+
+ // Encrypt the password using Sha256 into plaintext
+ const password = await hashPassword(payload.password)
+
+ // Create a session
+ const session = await useAppSession()
+
+ if (found) {
+ if (found.password !== password) {
+ return {
+ error: true,
+ userExists: true,
+ message: 'User already exists',
+ }
+ }
+
+ // Store the user's email in the session
+ await session.update({
+ userEmail: found.email,
+ })
+
+ // Redirect to the prev page stored in the "redirect" search param
+ throw redirect({
+ href: payload.redirectUrl || '/',
+ })
+ }
+
+ // Create the user
+ const user = await prismaClient.user.create({
+ data: {
+ email: payload.email,
+ password,
+ },
+ })
+
+ // Store the user's email in the session
+ await session.update({
+ userEmail: user.email,
+ })
+
+ // Redirect to the prev page stored in the "redirect" search param
+ throw redirect({
+ href: payload.redirectUrl || '/',
+ })
+ })
+
+export const Route = createFileRoute('/signup')({
+ component: SignupComp,
+})
+
+function SignupComp() {
+ const signupMutation = useMutation({
+ fn: useServerFn(signupFn),
+ })
+
+ return (
+ {
+ const formData = new FormData(e.target as any as HTMLFormElement)
+
+ signupMutation.mutate({
+ data: {
+ email: formData.get('email') as string,
+ password: formData.get('password') as string,
+ },
+ })
+ }}
+ afterSubmit={
+ signupMutation.data()?.error ? (
+ <>
+ {signupMutation.data()?.message}
+ >
+ ) : null
+ }
+ />
+ )
+}
diff --git a/e2e/solid-start/basic-auth/src/styles/app.css b/e2e/solid-start/basic-auth/src/styles/app.css
new file mode 100644
index 00000000000..c53c8706654
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/styles/app.css
@@ -0,0 +1,22 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ html {
+ color-scheme: light dark;
+ }
+
+ * {
+ @apply border-gray-200 dark:border-gray-800;
+ }
+
+ html,
+ body {
+ @apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200;
+ }
+
+ .using-mouse * {
+ outline: none !important;
+ }
+}
diff --git a/e2e/solid-start/basic-auth/src/utils/posts.ts b/e2e/solid-start/basic-auth/src/utils/posts.ts
new file mode 100644
index 00000000000..0408542344c
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/utils/posts.ts
@@ -0,0 +1,37 @@
+import { notFound } from '@tanstack/solid-router'
+import { createServerFn } from '@tanstack/solid-start'
+import axios from 'redaxios'
+
+export type PostType = {
+ id: string
+ title: string
+ body: string
+}
+
+export const fetchPost = createServerFn({ method: 'GET' })
+ .inputValidator((postId: string) => postId)
+ .handler(async ({ data: postId }) => {
+ console.info(`Fetching post with id ${postId}...`)
+ const post = await axios
+ .get(`https://jsonplaceholder.typicode.com/posts/${postId}`)
+ .then((r) => r.data)
+ .catch((err) => {
+ console.error(err)
+ if (err.status === 404) {
+ throw notFound()
+ }
+ throw err
+ })
+
+ return post
+ })
+
+export const fetchPosts = createServerFn({ method: 'GET' }).handler(
+ async () => {
+ console.info('Fetching posts...')
+ await new Promise((r) => setTimeout(r, 1000))
+ return axios
+ .get>('https://jsonplaceholder.typicode.com/posts')
+ .then((r) => r.data.slice(0, 10))
+ },
+)
diff --git a/e2e/solid-start/basic-auth/src/utils/prisma.ts b/e2e/solid-start/basic-auth/src/utils/prisma.ts
new file mode 100644
index 00000000000..74f5137b465
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/utils/prisma.ts
@@ -0,0 +1,16 @@
+import crypto from 'node:crypto'
+import { PrismaClient } from '@prisma/client'
+
+export const prismaClient = new PrismaClient()
+
+export function hashPassword(password: string) {
+ return new Promise((resolve, reject) => {
+ crypto.pbkdf2(password, 'salt', 100000, 64, 'sha256', (err, derivedKey) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve(derivedKey.toString('hex'))
+ }
+ })
+ })
+}
diff --git a/e2e/solid-start/basic-auth/src/utils/seo.ts b/e2e/solid-start/basic-auth/src/utils/seo.ts
new file mode 100644
index 00000000000..d18ad84b74e
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/utils/seo.ts
@@ -0,0 +1,33 @@
+export const seo = ({
+ title,
+ description,
+ keywords,
+ image,
+}: {
+ title: string
+ description?: string
+ image?: string
+ keywords?: string
+}) => {
+ const tags = [
+ { title },
+ { name: 'description', content: description },
+ { name: 'keywords', content: keywords },
+ { name: 'twitter:title', content: title },
+ { name: 'twitter:description', content: description },
+ { name: 'twitter:creator', content: '@tannerlinsley' },
+ { name: 'twitter:site', content: '@tannerlinsley' },
+ { name: 'og:type', content: 'website' },
+ { name: 'og:title', content: title },
+ { name: 'og:description', content: description },
+ ...(image
+ ? [
+ { name: 'twitter:image', content: image },
+ { name: 'twitter:card', content: 'summary_large_image' },
+ { name: 'og:image', content: image },
+ ]
+ : []),
+ ]
+
+ return tags
+}
diff --git a/e2e/solid-start/basic-auth/src/utils/session.ts b/e2e/solid-start/basic-auth/src/utils/session.ts
new file mode 100644
index 00000000000..120a410b60f
--- /dev/null
+++ b/e2e/solid-start/basic-auth/src/utils/session.ts
@@ -0,0 +1,13 @@
+// src/services/session.server.ts
+import { useSession } from '@tanstack/solid-start/server'
+import type { User } from '@prisma/client'
+
+type SessionUser = {
+ userEmail: User['email']
+}
+
+export function useAppSession() {
+ return useSession({
+ password: 'ChangeThisBeforeShippingToProdOrYouWillBeFired',
+ })
+}
diff --git a/e2e/solid-start/basic-auth/tailwind.config.mjs b/e2e/solid-start/basic-auth/tailwind.config.mjs
new file mode 100644
index 00000000000..e49f4eb776e
--- /dev/null
+++ b/e2e/solid-start/basic-auth/tailwind.config.mjs
@@ -0,0 +1,4 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: ['./src/**/*.{js,jsx,ts,tsx}'],
+}
diff --git a/e2e/solid-start/basic-auth/tests/app.spec.ts b/e2e/solid-start/basic-auth/tests/app.spec.ts
new file mode 100644
index 00000000000..e96cb400b04
--- /dev/null
+++ b/e2e/solid-start/basic-auth/tests/app.spec.ts
@@ -0,0 +1,60 @@
+import { expect, test } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+async function signup(
+ page: Page,
+ baseUrl: string,
+ email: string,
+ password: string,
+) {
+ await page.goto(baseUrl + '/signup')
+ await page.fill('input[name="email"]', email)
+ await page.fill('input[name="password"]', password)
+ await page.click('button[type="submit"]')
+}
+
+async function login(
+ page: Page,
+ email: string,
+ password: string,
+ signupOnFail = false,
+) {
+ await page.goto('/login')
+ await page.fill('input[name="email"]', email)
+ await page.fill('input[name="password"]', password)
+ await page.click('button[type="submit"]')
+
+ if (signupOnFail) {
+ await page.waitForSelector('text=User not found')
+ await page.click('button:has-text("Sign up instead?")')
+ await page.waitForSelector('text=Logout')
+ }
+}
+
+test('Posts redirects to login when not authenticated', async ({ page }) => {
+ await page.goto('/posts')
+ await expect(page.locator('h1')).toContainText('Login')
+})
+
+test('Login fails with user not found', async ({ page }) => {
+ await login(page, 'bad@gmail.com', 'badpassword')
+ expect(page.getByText('User not found')).toBeTruthy()
+})
+
+test('Login fails with incorrect password', async ({ page }) => {
+ await signup(page, 'test@gmail.com', 'badpassword')
+ expect(page.getByText('Incorrect password')).toBeTruthy()
+})
+
+test('Can sign up from a not found user', async ({ page }) => {
+ await login(page, 'test2@gmail.com', 'badpassword', true)
+ expect(page.getByText('test@gmail.com')).toBeTruthy()
+})
+
+test('Navigating to post after logging in', async ({ page }) => {
+ await login(page, 'test@gmail.com', 'test')
+ await new Promise((r) => setTimeout(r, 1000))
+ await page.getByRole('link', { name: 'Posts' }).click()
+ await page.getByRole('link', { name: 'sunt aut facere repe' }).click()
+ await expect(page.getByRole('heading')).toContainText('sunt aut facere')
+})
diff --git a/e2e/solid-start/basic-auth/tests/mock-db-setup.test.ts b/e2e/solid-start/basic-auth/tests/mock-db-setup.test.ts
new file mode 100644
index 00000000000..742381a0475
--- /dev/null
+++ b/e2e/solid-start/basic-auth/tests/mock-db-setup.test.ts
@@ -0,0 +1,21 @@
+import { test as setup } from '@playwright/test'
+
+import { PrismaClient } from '@prisma/client'
+
+const prismaClient = new PrismaClient()
+
+setup('create new database', async () => {
+ if (
+ await prismaClient.user.findUnique({
+ where: {
+ email: 'test2@gmail.com',
+ },
+ })
+ ) {
+ await prismaClient.user.delete({
+ where: {
+ email: 'test2@gmail.com',
+ },
+ })
+ }
+})
diff --git a/e2e/solid-start/basic-auth/tests/mock-db-teardown.test.ts b/e2e/solid-start/basic-auth/tests/mock-db-teardown.test.ts
new file mode 100644
index 00000000000..d933c7cb089
--- /dev/null
+++ b/e2e/solid-start/basic-auth/tests/mock-db-teardown.test.ts
@@ -0,0 +1,21 @@
+import { test as teardown } from '@playwright/test'
+
+import { PrismaClient } from '@prisma/client'
+
+const prismaClient = new PrismaClient()
+
+teardown('create new database', async () => {
+ if (
+ await prismaClient.user.findUnique({
+ where: {
+ email: 'test2@gmail.com',
+ },
+ })
+ ) {
+ await prismaClient.user.delete({
+ where: {
+ email: 'test2@gmail.com',
+ },
+ })
+ }
+})
diff --git a/e2e/solid-start/basic-auth/tsconfig.json b/e2e/solid-start/basic-auth/tsconfig.json
new file mode 100644
index 00000000000..a40235b863f
--- /dev/null
+++ b/e2e/solid-start/basic-auth/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "include": ["**/*.ts", "**/*.tsx"],
+ "compilerOptions": {
+ "strict": true,
+ "esModuleInterop": true,
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
+ "isolatedModules": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "target": "ES2022",
+ "allowJs": true,
+ "forceConsistentCasingInFileNames": true,
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["./src/*"]
+ },
+ "noEmit": true
+ }
+}
diff --git a/e2e/solid-start/basic-auth/vite.config.ts b/e2e/solid-start/basic-auth/vite.config.ts
new file mode 100644
index 00000000000..cba8396d96a
--- /dev/null
+++ b/e2e/solid-start/basic-auth/vite.config.ts
@@ -0,0 +1,14 @@
+import { defineConfig } from 'vite'
+import tsConfigPaths from 'vite-tsconfig-paths'
+import { tanstackStart } from '@tanstack/solid-start/plugin/vite'
+import viteSolid from 'vite-plugin-solid'
+
+export default defineConfig({
+ plugins: [
+ tsConfigPaths({
+ projects: ['./tsconfig.json'],
+ }),
+ tanstackStart(),
+ viteSolid({ ssr: true }),
+ ],
+})
diff --git a/examples/solid/start-basic-auth/.env b/examples/solid/start-basic-auth/.env
new file mode 100644
index 00000000000..c498ab59bf1
--- /dev/null
+++ b/examples/solid/start-basic-auth/.env
@@ -0,0 +1,7 @@
+# Environment variables declared in this file are automatically made available to Prisma.
+# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
+
+# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
+# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
+
+DATABASE_URL="file:./dev.db"
\ No newline at end of file
diff --git a/examples/solid/start-basic-auth/.gitignore b/examples/solid/start-basic-auth/.gitignore
new file mode 100644
index 00000000000..28185491585
--- /dev/null
+++ b/examples/solid/start-basic-auth/.gitignore
@@ -0,0 +1,18 @@
+node_modules
+package-lock.json
+yarn.lock
+
+!.env
+.DS_Store
+.cache
+.vercel
+.output
+/build/
+/api/
+/server/build
+/public/build# Sentry Config File
+.env.sentry-build-plugin
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
diff --git a/examples/solid/start-basic-auth/.prettierignore b/examples/solid/start-basic-auth/.prettierignore
new file mode 100644
index 00000000000..2be5eaa6ece
--- /dev/null
+++ b/examples/solid/start-basic-auth/.prettierignore
@@ -0,0 +1,4 @@
+**/build
+**/public
+pnpm-lock.yaml
+routeTree.gen.ts
\ No newline at end of file
diff --git a/examples/solid/start-basic-auth/.vscode/settings.json b/examples/solid/start-basic-auth/.vscode/settings.json
new file mode 100644
index 00000000000..00b5278e580
--- /dev/null
+++ b/examples/solid/start-basic-auth/.vscode/settings.json
@@ -0,0 +1,11 @@
+{
+ "files.watcherExclude": {
+ "**/routeTree.gen.ts": true
+ },
+ "search.exclude": {
+ "**/routeTree.gen.ts": true
+ },
+ "files.readonlyInclude": {
+ "**/routeTree.gen.ts": true
+ }
+}
diff --git a/examples/solid/start-basic-auth/README.md b/examples/solid/start-basic-auth/README.md
new file mode 100644
index 00000000000..90cba4aac1e
--- /dev/null
+++ b/examples/solid/start-basic-auth/README.md
@@ -0,0 +1,72 @@
+# Welcome to TanStack.com!
+
+This site is built with TanStack Router!
+
+- [TanStack Router Docs](https://tanstack.com/router)
+
+It's deployed automagically with Netlify!
+
+- [Netlify](https://netlify.com/)
+
+## Development
+
+From your terminal:
+
+```sh
+pnpm install
+pnpm dev
+```
+
+This starts your app in development mode, rebuilding assets on file changes.
+
+## Editing and previewing the docs of TanStack projects locally
+
+The documentations for all TanStack projects except for `React Charts` are hosted on [https://tanstack.com](https://tanstack.com), powered by this TanStack Router app.
+In production, the markdown doc pages are fetched from the GitHub repos of the projects, but in development they are read from the local file system.
+
+Follow these steps if you want to edit the doc pages of a project (in these steps we'll assume it's [`TanStack/form`](https://github.com/tanstack/form)) and preview them locally :
+
+1. Create a new directory called `tanstack`.
+
+```sh
+mkdir tanstack
+```
+
+2. Enter the directory and clone this repo and the repo of the project there.
+
+```sh
+cd tanstack
+git clone git@github.com:TanStack/tanstack.com.git
+git clone git@github.com:TanStack/form.git
+```
+
+> [!NOTE]
+> Your `tanstack` directory should look like this:
+>
+> ```
+> tanstack/
+> |
+> +-- form/
+> |
+> +-- tanstack.com/
+> ```
+
+> [!WARNING]
+> Make sure the name of the directory in your local file system matches the name of the project's repo. For example, `tanstack/form` must be cloned into `form` (this is the default) instead of `some-other-name`, because that way, the doc pages won't be found.
+
+3. Enter the `tanstack/tanstack.com` directory, install the dependencies and run the app in dev mode:
+
+```sh
+cd tanstack.com
+pnpm i
+# The app will run on https://localhost:3000 by default
+pnpm dev
+```
+
+4. Now you can visit http://localhost:3000/form/latest/docs/overview in the browser and see the changes you make in `tanstack/form/docs`.
+
+> [!NOTE]
+> The updated pages need to be manually reloaded in the browser.
+
+> [!WARNING]
+> You will need to update the `docs/config.json` file (in the project's repo) if you add a new doc page!
diff --git a/examples/solid/start-basic-auth/package.json b/examples/solid/start-basic-auth/package.json
new file mode 100644
index 00000000000..9cac4db9b8d
--- /dev/null
+++ b/examples/solid/start-basic-auth/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "tanstack-solid-start-example-basic-auth",
+ "private": true,
+ "sideEffects": false,
+ "type": "module",
+ "scripts": {
+ "dev": "vite dev",
+ "build": "vite build",
+ "start": "vite start",
+ "prisma-generate": "prisma generate"
+ },
+ "dependencies": {
+ "@prisma/client": "5.22.0",
+ "@tanstack/solid-router": "^1.133.20",
+ "@tanstack/solid-router-devtools": "^1.133.20",
+ "@tanstack/solid-start": "^1.133.20",
+ "prisma": "^5.22.0",
+ "solid-js": "^1.9.5",
+ "redaxios": "^0.5.1",
+ "tailwind-merge": "^2.6.0"
+ },
+ "devDependencies": {
+ "vite-plugin-solid": "^2.11.9",
+ "@types/node": "^22.5.4",
+ "postcss": "^8.5.1",
+ "autoprefixer": "^10.4.20",
+ "tailwindcss": "^3.4.17",
+ "typescript": "^5.7.2",
+ "vite": "^7.1.7",
+ "vite-tsconfig-paths": "^5.1.4"
+ }
+}
diff --git a/examples/solid/start-basic-auth/postcss.config.mjs b/examples/solid/start-basic-auth/postcss.config.mjs
new file mode 100644
index 00000000000..2e7af2b7f1a
--- /dev/null
+++ b/examples/solid/start-basic-auth/postcss.config.mjs
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/examples/solid/start-basic-auth/prisma/dev.db b/examples/solid/start-basic-auth/prisma/dev.db
new file mode 100644
index 00000000000..5f4ab51a029
Binary files /dev/null and b/examples/solid/start-basic-auth/prisma/dev.db differ
diff --git a/examples/solid/start-basic-auth/prisma/migrations/20240811183753_init/migration.sql b/examples/solid/start-basic-auth/prisma/migrations/20240811183753_init/migration.sql
new file mode 100644
index 00000000000..4512a8f7823
--- /dev/null
+++ b/examples/solid/start-basic-auth/prisma/migrations/20240811183753_init/migration.sql
@@ -0,0 +1,8 @@
+-- CreateTable
+CREATE TABLE "User" (
+ "email" TEXT NOT NULL PRIMARY KEY,
+ "password" TEXT NOT NULL
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
diff --git a/examples/solid/start-basic-auth/prisma/migrations/migration_lock.toml b/examples/solid/start-basic-auth/prisma/migrations/migration_lock.toml
new file mode 100644
index 00000000000..e5e5c4705ab
--- /dev/null
+++ b/examples/solid/start-basic-auth/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "sqlite"
\ No newline at end of file
diff --git a/examples/solid/start-basic-auth/prisma/schema.prisma b/examples/solid/start-basic-auth/prisma/schema.prisma
new file mode 100644
index 00000000000..3544834310f
--- /dev/null
+++ b/examples/solid/start-basic-auth/prisma/schema.prisma
@@ -0,0 +1,16 @@
+// This is your Prisma schema file,
+// learn more about it in the docs: https://pris.ly/d/prisma-schema
+
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "sqlite"
+ url = env("DATABASE_URL")
+}
+
+model User {
+ email String @id @unique
+ password String
+}
\ No newline at end of file
diff --git a/examples/solid/start-basic-auth/public/android-chrome-192x192.png b/examples/solid/start-basic-auth/public/android-chrome-192x192.png
new file mode 100644
index 00000000000..09c8324f8c6
Binary files /dev/null and b/examples/solid/start-basic-auth/public/android-chrome-192x192.png differ
diff --git a/examples/solid/start-basic-auth/public/android-chrome-512x512.png b/examples/solid/start-basic-auth/public/android-chrome-512x512.png
new file mode 100644
index 00000000000..11d626ea3d0
Binary files /dev/null and b/examples/solid/start-basic-auth/public/android-chrome-512x512.png differ
diff --git a/examples/solid/start-basic-auth/public/apple-touch-icon.png b/examples/solid/start-basic-auth/public/apple-touch-icon.png
new file mode 100644
index 00000000000..5a9423cc02c
Binary files /dev/null and b/examples/solid/start-basic-auth/public/apple-touch-icon.png differ
diff --git a/examples/solid/start-basic-auth/public/favicon-16x16.png b/examples/solid/start-basic-auth/public/favicon-16x16.png
new file mode 100644
index 00000000000..e3389b00443
Binary files /dev/null and b/examples/solid/start-basic-auth/public/favicon-16x16.png differ
diff --git a/examples/solid/start-basic-auth/public/favicon-32x32.png b/examples/solid/start-basic-auth/public/favicon-32x32.png
new file mode 100644
index 00000000000..900c77d444c
Binary files /dev/null and b/examples/solid/start-basic-auth/public/favicon-32x32.png differ
diff --git a/examples/solid/start-basic-auth/public/favicon.ico b/examples/solid/start-basic-auth/public/favicon.ico
new file mode 100644
index 00000000000..1a1751676f7
Binary files /dev/null and b/examples/solid/start-basic-auth/public/favicon.ico differ
diff --git a/examples/solid/start-basic-auth/public/favicon.png b/examples/solid/start-basic-auth/public/favicon.png
new file mode 100644
index 00000000000..1e77bc06091
Binary files /dev/null and b/examples/solid/start-basic-auth/public/favicon.png differ
diff --git a/examples/solid/start-basic-auth/public/site.webmanifest b/examples/solid/start-basic-auth/public/site.webmanifest
new file mode 100644
index 00000000000..fa99de77db6
--- /dev/null
+++ b/examples/solid/start-basic-auth/public/site.webmanifest
@@ -0,0 +1,19 @@
+{
+ "name": "",
+ "short_name": "",
+ "icons": [
+ {
+ "src": "/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
diff --git a/examples/solid/start-basic-auth/src/components/Auth.tsx b/examples/solid/start-basic-auth/src/components/Auth.tsx
new file mode 100644
index 00000000000..40e44b8c5a4
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/components/Auth.tsx
@@ -0,0 +1,59 @@
+import type { JSX } from 'solid-js'
+
+export function Auth({
+ actionText,
+ onSubmit,
+ status,
+ afterSubmit,
+}: {
+ actionText: string
+ onSubmit: (e: HTMLFormElement) => void
+ status: 'pending' | 'idle' | 'success' | 'error'
+ afterSubmit?: JSX.Element
+}) {
+ return (
+
+ )
+}
diff --git a/examples/solid/start-basic-auth/src/components/DefaultCatchBoundary.tsx b/examples/solid/start-basic-auth/src/components/DefaultCatchBoundary.tsx
new file mode 100644
index 00000000000..32aed20e675
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/components/DefaultCatchBoundary.tsx
@@ -0,0 +1,53 @@
+import {
+ ErrorComponent,
+ Link,
+ rootRouteId,
+ useMatch,
+ useRouter,
+} from '@tanstack/solid-router'
+import type { ErrorComponentProps } from '@tanstack/solid-router'
+
+export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
+ const router = useRouter()
+ const isRoot = useMatch({
+ strict: false,
+ select: (state) => state.id === rootRouteId,
+ })
+
+ console.error(error)
+
+ return (
+
+
+
+
+ {isRoot() ? (
+
+ Home
+
+ ) : (
+ {
+ e.preventDefault()
+ window.history.back()
+ }}
+ >
+ Go Back
+
+ )}
+
+
+ )
+}
diff --git a/examples/solid/start-basic-auth/src/components/Login.tsx b/examples/solid/start-basic-auth/src/components/Login.tsx
new file mode 100644
index 00000000000..ae2b270393a
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/components/Login.tsx
@@ -0,0 +1,71 @@
+import { useRouter } from '@tanstack/solid-router'
+import { useServerFn } from '@tanstack/solid-start'
+import { useMutation } from '../hooks/useMutation'
+import { loginFn } from '../routes/_authed'
+import { Auth } from './Auth'
+import { signupFn } from '~/routes/signup'
+
+export function Login() {
+ const router = useRouter()
+
+ const loginMutation = useMutation({
+ fn: loginFn,
+ onSuccess: async (ctx) => {
+ if (!ctx.data?.error) {
+ await router.invalidate()
+ router.navigate({ to: '/' })
+ return
+ }
+ },
+ })
+
+ const signupMutation = useMutation({
+ fn: useServerFn(signupFn),
+ })
+
+ return (
+ {
+ const formData = new FormData(e.target as any as HTMLFormElement)
+
+ loginMutation.mutate({
+ data: {
+ email: formData.get('email') as string,
+ password: formData.get('password') as string,
+ },
+ })
+ }}
+ afterSubmit={
+ loginMutation.data() ? (
+ <>
+ {loginMutation.data()?.message}
+ {loginMutation.data()?.userNotFound ? (
+
+
+
+ ) : null}
+ >
+ ) : null
+ }
+ />
+ )
+}
diff --git a/examples/solid/start-basic-auth/src/components/NotFound.tsx b/examples/solid/start-basic-auth/src/components/NotFound.tsx
new file mode 100644
index 00000000000..ca4c1960fa1
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/components/NotFound.tsx
@@ -0,0 +1,25 @@
+import { Link } from '@tanstack/solid-router'
+
+export function NotFound({ children }: { children?: any }) {
+ return (
+
+
+ {children ||
The page you are looking for does not exist.
}
+
+
+
+
+ Start Over
+
+
+
+ )
+}
diff --git a/examples/solid/start-basic-auth/src/hooks/useMutation.ts b/examples/solid/start-basic-auth/src/hooks/useMutation.ts
new file mode 100644
index 00000000000..102ecf87fc8
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/hooks/useMutation.ts
@@ -0,0 +1,41 @@
+import * as Solid from 'solid-js'
+
+export function useMutation(opts: {
+ fn: (variables: TVariables) => Promise
+ onSuccess?: (ctx: { data: TData }) => void | Promise
+}) {
+ const [submittedAt, setSubmittedAt] = Solid.createSignal()
+ const [variables, setVariables] = Solid.createSignal()
+ const [error, setError] = Solid.createSignal()
+ const [data, setData] = Solid.createSignal()
+ const [status, setStatus] = Solid.createSignal<
+ 'idle' | 'pending' | 'success' | 'error'
+ >('idle')
+
+ const mutate = async (variables: TVariables): Promise => {
+ setStatus('pending')
+ setSubmittedAt(Date.now())
+ setVariables(variables as any)
+ //
+ try {
+ const data = await opts.fn(variables)
+ await opts.onSuccess?.({ data })
+ setStatus('success')
+ setError(undefined)
+ setData(data as any)
+ return data
+ } catch (err: any) {
+ setStatus('error')
+ setError(err)
+ }
+ }
+
+ return {
+ status,
+ variables,
+ submittedAt,
+ mutate,
+ error,
+ data,
+ }
+}
diff --git a/examples/solid/start-basic-auth/src/routeTree.gen.ts b/examples/solid/start-basic-auth/src/routeTree.gen.ts
new file mode 100644
index 00000000000..3b5f50dde1b
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/routeTree.gen.ts
@@ -0,0 +1,224 @@
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file was automatically generated by TanStack Router.
+// You should NOT make any changes in this file as it will be overwritten.
+// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
+
+import { Route as rootRouteImport } from './routes/__root'
+import { Route as SignupRouteImport } from './routes/signup'
+import { Route as LogoutRouteImport } from './routes/logout'
+import { Route as LoginRouteImport } from './routes/login'
+import { Route as AuthedRouteImport } from './routes/_authed'
+import { Route as IndexRouteImport } from './routes/index'
+import { Route as AuthedPostsRouteRouteImport } from './routes/_authed/posts.route'
+import { Route as AuthedPostsIndexRouteImport } from './routes/_authed/posts.index'
+import { Route as AuthedPostsPostIdRouteImport } from './routes/_authed/posts.$postId'
+
+const SignupRoute = SignupRouteImport.update({
+ id: '/signup',
+ path: '/signup',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const LogoutRoute = LogoutRouteImport.update({
+ id: '/logout',
+ path: '/logout',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const LoginRoute = LoginRouteImport.update({
+ id: '/login',
+ path: '/login',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const AuthedRoute = AuthedRouteImport.update({
+ id: '/_authed',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const IndexRoute = IndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRouteImport,
+} as any)
+const AuthedPostsRouteRoute = AuthedPostsRouteRouteImport.update({
+ id: '/posts',
+ path: '/posts',
+ getParentRoute: () => AuthedRoute,
+} as any)
+const AuthedPostsIndexRoute = AuthedPostsIndexRouteImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => AuthedPostsRouteRoute,
+} as any)
+const AuthedPostsPostIdRoute = AuthedPostsPostIdRouteImport.update({
+ id: '/$postId',
+ path: '/$postId',
+ getParentRoute: () => AuthedPostsRouteRoute,
+} as any)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '/login': typeof LoginRoute
+ '/logout': typeof LogoutRoute
+ '/signup': typeof SignupRoute
+ '/posts': typeof AuthedPostsRouteRouteWithChildren
+ '/posts/$postId': typeof AuthedPostsPostIdRoute
+ '/posts/': typeof AuthedPostsIndexRoute
+}
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '/login': typeof LoginRoute
+ '/logout': typeof LogoutRoute
+ '/signup': typeof SignupRoute
+ '/posts/$postId': typeof AuthedPostsPostIdRoute
+ '/posts': typeof AuthedPostsIndexRoute
+}
+export interface FileRoutesById {
+ __root__: typeof rootRouteImport
+ '/': typeof IndexRoute
+ '/_authed': typeof AuthedRouteWithChildren
+ '/login': typeof LoginRoute
+ '/logout': typeof LogoutRoute
+ '/signup': typeof SignupRoute
+ '/_authed/posts': typeof AuthedPostsRouteRouteWithChildren
+ '/_authed/posts/$postId': typeof AuthedPostsPostIdRoute
+ '/_authed/posts/': typeof AuthedPostsIndexRoute
+}
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths:
+ | '/'
+ | '/login'
+ | '/logout'
+ | '/signup'
+ | '/posts'
+ | '/posts/$postId'
+ | '/posts/'
+ fileRoutesByTo: FileRoutesByTo
+ to: '/' | '/login' | '/logout' | '/signup' | '/posts/$postId' | '/posts'
+ id:
+ | '__root__'
+ | '/'
+ | '/_authed'
+ | '/login'
+ | '/logout'
+ | '/signup'
+ | '/_authed/posts'
+ | '/_authed/posts/$postId'
+ | '/_authed/posts/'
+ fileRoutesById: FileRoutesById
+}
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ AuthedRoute: typeof AuthedRouteWithChildren
+ LoginRoute: typeof LoginRoute
+ LogoutRoute: typeof LogoutRoute
+ SignupRoute: typeof SignupRoute
+}
+
+declare module '@tanstack/solid-router' {
+ interface FileRoutesByPath {
+ '/signup': {
+ id: '/signup'
+ path: '/signup'
+ fullPath: '/signup'
+ preLoaderRoute: typeof SignupRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/logout': {
+ id: '/logout'
+ path: '/logout'
+ fullPath: '/logout'
+ preLoaderRoute: typeof LogoutRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/login': {
+ id: '/login'
+ path: '/login'
+ fullPath: '/login'
+ preLoaderRoute: typeof LoginRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/_authed': {
+ id: '/_authed'
+ path: ''
+ fullPath: ''
+ preLoaderRoute: typeof AuthedRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/_authed/posts': {
+ id: '/_authed/posts'
+ path: '/posts'
+ fullPath: '/posts'
+ preLoaderRoute: typeof AuthedPostsRouteRouteImport
+ parentRoute: typeof AuthedRoute
+ }
+ '/_authed/posts/': {
+ id: '/_authed/posts/'
+ path: '/'
+ fullPath: '/posts/'
+ preLoaderRoute: typeof AuthedPostsIndexRouteImport
+ parentRoute: typeof AuthedPostsRouteRoute
+ }
+ '/_authed/posts/$postId': {
+ id: '/_authed/posts/$postId'
+ path: '/$postId'
+ fullPath: '/posts/$postId'
+ preLoaderRoute: typeof AuthedPostsPostIdRouteImport
+ parentRoute: typeof AuthedPostsRouteRoute
+ }
+ }
+}
+
+interface AuthedPostsRouteRouteChildren {
+ AuthedPostsPostIdRoute: typeof AuthedPostsPostIdRoute
+ AuthedPostsIndexRoute: typeof AuthedPostsIndexRoute
+}
+
+const AuthedPostsRouteRouteChildren: AuthedPostsRouteRouteChildren = {
+ AuthedPostsPostIdRoute: AuthedPostsPostIdRoute,
+ AuthedPostsIndexRoute: AuthedPostsIndexRoute,
+}
+
+const AuthedPostsRouteRouteWithChildren =
+ AuthedPostsRouteRoute._addFileChildren(AuthedPostsRouteRouteChildren)
+
+interface AuthedRouteChildren {
+ AuthedPostsRouteRoute: typeof AuthedPostsRouteRouteWithChildren
+}
+
+const AuthedRouteChildren: AuthedRouteChildren = {
+ AuthedPostsRouteRoute: AuthedPostsRouteRouteWithChildren,
+}
+
+const AuthedRouteWithChildren =
+ AuthedRoute._addFileChildren(AuthedRouteChildren)
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ AuthedRoute: AuthedRouteWithChildren,
+ LoginRoute: LoginRoute,
+ LogoutRoute: LogoutRoute,
+ SignupRoute: SignupRoute,
+}
+export const routeTree = rootRouteImport
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+import type { getRouter } from './router.tsx'
+import type { createStart } from '@tanstack/solid-start'
+declare module '@tanstack/solid-start' {
+ interface Register {
+ ssr: true
+ router: Awaited>
+ }
+}
diff --git a/examples/solid/start-basic-auth/src/router.tsx b/examples/solid/start-basic-auth/src/router.tsx
new file mode 100644
index 00000000000..da050c7db65
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/router.tsx
@@ -0,0 +1,22 @@
+import { createRouter } from '@tanstack/solid-router'
+import { routeTree } from './routeTree.gen'
+import { DefaultCatchBoundary } from './components/DefaultCatchBoundary'
+import { NotFound } from './components/NotFound'
+
+export function getRouter() {
+ const router = createRouter({
+ routeTree,
+ defaultPreload: 'intent',
+ defaultErrorComponent: DefaultCatchBoundary,
+ defaultNotFoundComponent: () => ,
+ scrollRestoration: true,
+ })
+
+ return router
+}
+
+declare module '@tanstack/solid-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/examples/solid/start-basic-auth/src/routes/__root.tsx b/examples/solid/start-basic-auth/src/routes/__root.tsx
new file mode 100644
index 00000000000..3aa0bfa18b7
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/routes/__root.tsx
@@ -0,0 +1,143 @@
+///
+import {
+ HeadContent,
+ Link,
+ Outlet,
+ Scripts,
+ createRootRoute,
+} from '@tanstack/solid-router'
+import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools'
+import { createServerFn } from '@tanstack/solid-start'
+import { HydrationScript } from 'solid-js/web'
+import type * as Solid from 'solid-js'
+import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary.js'
+import { NotFound } from '~/components/NotFound.js'
+import appCss from '~/styles/app.css?url'
+import { seo } from '~/utils/seo.js'
+import { useAppSession } from '~/utils/session.js'
+
+const fetchUser = createServerFn({ method: 'GET' }).handler(async () => {
+ // We need to auth on the server so we have access to secure cookies
+ const session = await useAppSession()
+
+ if (!session.data.userEmail) {
+ return null
+ }
+
+ return {
+ email: session.data.userEmail,
+ }
+})
+
+export const Route = createRootRoute({
+ beforeLoad: async () => {
+ const user = await fetchUser()
+
+ return {
+ user,
+ }
+ },
+ head: () => ({
+ meta: [
+ {
+ charset: 'utf-8',
+ },
+ {
+ name: 'viewport',
+ content: 'width=device-width, initial-scale=1',
+ },
+ ...seo({
+ title:
+ 'TanStack Start | Type-Safe, Client-First, Full-Stack React Framework',
+ description: `TanStack Start is a type-safe, client-first, full-stack React framework. `,
+ }),
+ ],
+ links: [
+ { rel: 'stylesheet', href: appCss },
+ {
+ rel: 'apple-touch-icon',
+ sizes: '180x180',
+ href: '/apple-touch-icon.png',
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '32x32',
+ href: '/favicon-32x32.png',
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '16x16',
+ href: '/favicon-16x16.png',
+ },
+ { rel: 'manifest', href: '/site.webmanifest', color: '#fffff' },
+ { rel: 'icon', href: '/favicon.ico' },
+ ],
+ }),
+ errorComponent: (props) => {
+ return (
+
+
+
+ )
+ },
+ notFoundComponent: () => ,
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+ )
+}
+
+function RootDocument({ children }: { children: Solid.JSX.Element }) {
+ const routeContext = Route.useRouteContext()
+
+ return (
+
+
+
+
+
+
+
+
+ Home
+ {' '}
+
+ Posts
+
+
+ {routeContext().user ? (
+ <>
+ {routeContext().user?.email}
+ Logout
+ >
+ ) : (
+ Login
+ )}
+
+
+
+ {children}
+
+
+
+
+ )
+}
diff --git a/examples/solid/start-basic-auth/src/routes/_authed.tsx b/examples/solid/start-basic-auth/src/routes/_authed.tsx
new file mode 100644
index 00000000000..3caa846b223
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/routes/_authed.tsx
@@ -0,0 +1,58 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { createServerFn } from '@tanstack/solid-start'
+import { hashPassword, prismaClient } from '~/utils/prisma'
+import { Login } from '~/components/Login'
+import { useAppSession } from '~/utils/session'
+
+export const loginFn = createServerFn({ method: 'POST' })
+ .inputValidator((d: { email: string; password: string }) => d)
+ .handler(async ({ data }) => {
+ // Find the user
+ const user = await prismaClient.user.findUnique({
+ where: {
+ email: data.email,
+ },
+ })
+
+ // Check if the user exists
+ if (!user) {
+ return {
+ error: true,
+ userNotFound: true,
+ message: 'User not found',
+ }
+ }
+
+ // Check if the password is correct
+ const hashedPassword = await hashPassword(data.password)
+
+ if (user.password !== hashedPassword) {
+ return {
+ error: true,
+ message: 'Incorrect password',
+ }
+ }
+
+ // Create a session
+ const session = await useAppSession()
+
+ // Store the user's email in the session
+ await session.update({
+ userEmail: user.email,
+ })
+ })
+
+export const Route = createFileRoute('/_authed')({
+ beforeLoad: ({ context }) => {
+ if (!context.user) {
+ throw new Error('Not authenticated')
+ }
+ },
+ errorComponent: ({ error }) => {
+ if (error.message === 'Not authenticated') {
+ return
+ }
+
+ throw error
+ },
+})
diff --git a/examples/solid/start-basic-auth/src/routes/_authed/posts.$postId.tsx b/examples/solid/start-basic-auth/src/routes/_authed/posts.$postId.tsx
new file mode 100644
index 00000000000..bbbbd815388
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/routes/_authed/posts.$postId.tsx
@@ -0,0 +1,28 @@
+import { ErrorComponent, createFileRoute } from '@tanstack/solid-router'
+import type { ErrorComponentProps } from '@tanstack/solid-router'
+import { NotFound } from '~/components/NotFound.js'
+import { fetchPost } from '~/utils/posts.js'
+
+export function PostErrorComponent({ error }: ErrorComponentProps) {
+ return
+}
+
+function PostComponent() {
+ const post = Route.useLoaderData()
+
+ return (
+
+
{post().title}
+
{post().body}
+
+ )
+}
+
+export const Route = createFileRoute('/_authed/posts/$postId')({
+ loader: ({ params: { postId } }) => fetchPost({ data: postId }),
+ errorComponent: PostErrorComponent,
+ component: PostComponent,
+ notFoundComponent: () => {
+ return Post not found
+ },
+})
diff --git a/examples/solid/start-basic-auth/src/routes/_authed/posts.index.tsx b/examples/solid/start-basic-auth/src/routes/_authed/posts.index.tsx
new file mode 100644
index 00000000000..4ba0817e286
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/routes/_authed/posts.index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/_authed/posts/')({
+ component: PostsIndexComponent,
+})
+
+function PostsIndexComponent() {
+ return Select a post.
+}
diff --git a/examples/solid/start-basic-auth/src/routes/_authed/posts.route.tsx b/examples/solid/start-basic-auth/src/routes/_authed/posts.route.tsx
new file mode 100644
index 00000000000..256aaee50f4
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/routes/_authed/posts.route.tsx
@@ -0,0 +1,38 @@
+import { Link, Outlet, createFileRoute } from '@tanstack/solid-router'
+import { fetchPosts } from '~/utils/posts.js'
+
+export const Route = createFileRoute('/_authed/posts')({
+ loader: () => fetchPosts(),
+ component: PostsComponent,
+})
+
+function PostsComponent() {
+ const posts = Route.useLoaderData()
+
+ return (
+
+ )
+}
diff --git a/examples/solid/start-basic-auth/src/routes/index.tsx b/examples/solid/start-basic-auth/src/routes/index.tsx
new file mode 100644
index 00000000000..a128aeca0e1
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/routes/index.tsx
@@ -0,0 +1,13 @@
+import { createFileRoute } from '@tanstack/solid-router'
+
+export const Route = createFileRoute('/')({
+ component: Home,
+})
+
+function Home() {
+ return (
+
+
Welcome Home!!!
+
+ )
+}
diff --git a/examples/solid/start-basic-auth/src/routes/login.tsx b/examples/solid/start-basic-auth/src/routes/login.tsx
new file mode 100644
index 00000000000..90a75e6867c
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/routes/login.tsx
@@ -0,0 +1,10 @@
+import { createFileRoute } from '@tanstack/solid-router'
+import { Login } from '~/components/Login'
+
+export const Route = createFileRoute('/login')({
+ component: LoginComp,
+})
+
+function LoginComp() {
+ return
+}
diff --git a/examples/solid/start-basic-auth/src/routes/logout.tsx b/examples/solid/start-basic-auth/src/routes/logout.tsx
new file mode 100644
index 00000000000..f7d624ff153
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/routes/logout.tsx
@@ -0,0 +1,18 @@
+import { createFileRoute, redirect } from '@tanstack/solid-router'
+import { createServerFn } from '@tanstack/solid-start'
+import { useAppSession } from '~/utils/session'
+
+const logoutFn = createServerFn().handler(async () => {
+ const session = await useAppSession()
+
+ session.clear()
+
+ throw redirect({
+ href: '/',
+ })
+})
+
+export const Route = createFileRoute('/logout')({
+ preload: false,
+ loader: () => logoutFn(),
+})
diff --git a/examples/solid/start-basic-auth/src/routes/signup.tsx b/examples/solid/start-basic-auth/src/routes/signup.tsx
new file mode 100644
index 00000000000..020e07c029b
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/routes/signup.tsx
@@ -0,0 +1,97 @@
+import { createFileRoute, redirect } from '@tanstack/solid-router'
+import { createServerFn, useServerFn } from '@tanstack/solid-start'
+import { hashPassword, prismaClient } from '~/utils/prisma'
+import { useMutation } from '~/hooks/useMutation'
+import { Auth } from '~/components/Auth'
+import { useAppSession } from '~/utils/session'
+
+export const signupFn = createServerFn({ method: 'POST' })
+ .inputValidator(
+ (d: { email: string; password: string; redirectUrl?: string }) => d,
+ )
+ .handler(async ({ data }) => {
+ // Check if the user already exists
+ const found = await prismaClient.user.findUnique({
+ where: {
+ email: data.email,
+ },
+ })
+
+ // Encrypt the password using Sha256 into plaintext
+ const password = await hashPassword(data.password)
+
+ // Create a session
+ const session = await useAppSession()
+
+ if (found) {
+ if (found.password !== password) {
+ return {
+ error: true,
+ userExists: true,
+ message: 'User already exists',
+ }
+ }
+
+ // Store the user's email in the session
+ await session.update({
+ userEmail: found.email,
+ })
+
+ // Redirect to the prev page stored in the "redirect" search param
+ throw redirect({
+ href: data.redirectUrl || '/',
+ })
+ }
+
+ // Create the user
+ const user = await prismaClient.user.create({
+ data: {
+ email: data.email,
+ password,
+ },
+ })
+
+ // Store the user's email in the session
+ await session.update({
+ userEmail: user.email,
+ })
+
+ // Redirect to the prev page stored in the "redirect" search param
+ throw redirect({
+ href: data.redirectUrl || '/',
+ })
+ })
+
+export const Route = createFileRoute('/signup')({
+ component: SignupComp,
+})
+
+function SignupComp() {
+ const signupMutation = useMutation({
+ fn: useServerFn(signupFn),
+ })
+
+ return (
+ {
+ const formData = new FormData(e.target as any as HTMLFormElement)
+
+ signupMutation.mutate({
+ data: {
+ email: formData.get('email') as string,
+ password: formData.get('password') as string,
+ },
+ })
+ }}
+ afterSubmit={
+ signupMutation.data()?.error ? (
+ <>
+ {signupMutation.data()?.message}
+ >
+ ) : null
+ }
+ />
+ )
+}
diff --git a/examples/solid/start-basic-auth/src/styles/app.css b/examples/solid/start-basic-auth/src/styles/app.css
new file mode 100644
index 00000000000..c53c8706654
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/styles/app.css
@@ -0,0 +1,22 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ html {
+ color-scheme: light dark;
+ }
+
+ * {
+ @apply border-gray-200 dark:border-gray-800;
+ }
+
+ html,
+ body {
+ @apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200;
+ }
+
+ .using-mouse * {
+ outline: none !important;
+ }
+}
diff --git a/examples/solid/start-basic-auth/src/utils/posts.ts b/examples/solid/start-basic-auth/src/utils/posts.ts
new file mode 100644
index 00000000000..5c2e34c98dc
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/utils/posts.ts
@@ -0,0 +1,37 @@
+import { notFound } from '@tanstack/solid-router'
+import { createServerFn } from '@tanstack/solid-start'
+import axios from 'redaxios'
+
+export type PostType = {
+ id: string
+ title: string
+ body: string
+}
+
+export const fetchPost = createServerFn({ method: 'GET' })
+ .inputValidator((postId: string) => postId)
+ .handler(async ({ data }) => {
+ console.info(`Fetching post with id ${data}...`)
+ const post = await axios
+ .get(`https://jsonplaceholder.typicode.com/posts/${data}`)
+ .then((r) => r.data)
+ .catch((err) => {
+ console.error(err)
+ if (err.status === 404) {
+ throw notFound()
+ }
+ throw err
+ })
+
+ return post
+ })
+
+export const fetchPosts = createServerFn({ method: 'GET' }).handler(
+ async () => {
+ console.info('Fetching posts...')
+ await new Promise((r) => setTimeout(r, 1000))
+ return axios
+ .get>('https://jsonplaceholder.typicode.com/posts')
+ .then((r) => r.data.slice(0, 10))
+ },
+)
diff --git a/examples/solid/start-basic-auth/src/utils/prisma.ts b/examples/solid/start-basic-auth/src/utils/prisma.ts
new file mode 100644
index 00000000000..74f5137b465
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/utils/prisma.ts
@@ -0,0 +1,16 @@
+import crypto from 'node:crypto'
+import { PrismaClient } from '@prisma/client'
+
+export const prismaClient = new PrismaClient()
+
+export function hashPassword(password: string) {
+ return new Promise((resolve, reject) => {
+ crypto.pbkdf2(password, 'salt', 100000, 64, 'sha256', (err, derivedKey) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve(derivedKey.toString('hex'))
+ }
+ })
+ })
+}
diff --git a/examples/solid/start-basic-auth/src/utils/seo.ts b/examples/solid/start-basic-auth/src/utils/seo.ts
new file mode 100644
index 00000000000..d18ad84b74e
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/utils/seo.ts
@@ -0,0 +1,33 @@
+export const seo = ({
+ title,
+ description,
+ keywords,
+ image,
+}: {
+ title: string
+ description?: string
+ image?: string
+ keywords?: string
+}) => {
+ const tags = [
+ { title },
+ { name: 'description', content: description },
+ { name: 'keywords', content: keywords },
+ { name: 'twitter:title', content: title },
+ { name: 'twitter:description', content: description },
+ { name: 'twitter:creator', content: '@tannerlinsley' },
+ { name: 'twitter:site', content: '@tannerlinsley' },
+ { name: 'og:type', content: 'website' },
+ { name: 'og:title', content: title },
+ { name: 'og:description', content: description },
+ ...(image
+ ? [
+ { name: 'twitter:image', content: image },
+ { name: 'twitter:card', content: 'summary_large_image' },
+ { name: 'og:image', content: image },
+ ]
+ : []),
+ ]
+
+ return tags
+}
diff --git a/examples/solid/start-basic-auth/src/utils/session.ts b/examples/solid/start-basic-auth/src/utils/session.ts
new file mode 100644
index 00000000000..120a410b60f
--- /dev/null
+++ b/examples/solid/start-basic-auth/src/utils/session.ts
@@ -0,0 +1,13 @@
+// src/services/session.server.ts
+import { useSession } from '@tanstack/solid-start/server'
+import type { User } from '@prisma/client'
+
+type SessionUser = {
+ userEmail: User['email']
+}
+
+export function useAppSession() {
+ return useSession({
+ password: 'ChangeThisBeforeShippingToProdOrYouWillBeFired',
+ })
+}
diff --git a/examples/solid/start-basic-auth/tailwind.config.mjs b/examples/solid/start-basic-auth/tailwind.config.mjs
new file mode 100644
index 00000000000..e49f4eb776e
--- /dev/null
+++ b/examples/solid/start-basic-auth/tailwind.config.mjs
@@ -0,0 +1,4 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: ['./src/**/*.{js,jsx,ts,tsx}'],
+}
diff --git a/examples/solid/start-basic-auth/tsconfig.json b/examples/solid/start-basic-auth/tsconfig.json
new file mode 100644
index 00000000000..a40235b863f
--- /dev/null
+++ b/examples/solid/start-basic-auth/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "include": ["**/*.ts", "**/*.tsx"],
+ "compilerOptions": {
+ "strict": true,
+ "esModuleInterop": true,
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
+ "isolatedModules": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "target": "ES2022",
+ "allowJs": true,
+ "forceConsistentCasingInFileNames": true,
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["./src/*"]
+ },
+ "noEmit": true
+ }
+}
diff --git a/examples/solid/start-basic-auth/vite.config.ts b/examples/solid/start-basic-auth/vite.config.ts
new file mode 100644
index 00000000000..27a27a7b5a5
--- /dev/null
+++ b/examples/solid/start-basic-auth/vite.config.ts
@@ -0,0 +1,17 @@
+import { tanstackStart } from '@tanstack/solid-start/plugin/vite'
+import { defineConfig } from 'vite'
+import tsConfigPaths from 'vite-tsconfig-paths'
+import viteSolid from 'vite-plugin-solid'
+
+export default defineConfig({
+ server: {
+ port: 3000,
+ },
+ plugins: [
+ tsConfigPaths({
+ projects: ['./tsconfig.json'],
+ }),
+ tanstackStart(),
+ viteSolid({ ssr: true }),
+ ],
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c73b7ac041b..edbf26aab26 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2378,7 +2378,7 @@ importers:
version: 1.0.3(@rsbuild/core@1.2.4)
'@rsbuild/plugin-solid':
specifier: ^1.0.4
- version: 1.0.4(@babel/core@7.27.4)(@rsbuild/core@1.2.4)(solid-js@1.9.5)
+ version: 1.0.4(@babel/core@7.28.4)(@rsbuild/core@1.2.4)(solid-js@1.9.5)
'@tanstack/router-e2e-utils':
specifier: workspace:^
version: link:../../e2e-utils
@@ -2424,7 +2424,7 @@ importers:
version: 1.0.3(@rsbuild/core@1.2.4)
'@rsbuild/plugin-solid':
specifier: ^1.0.4
- version: 1.0.4(@babel/core@7.28.4)(@rsbuild/core@1.2.4)(solid-js@1.9.5)
+ version: 1.0.4(@babel/core@7.27.4)(@rsbuild/core@1.2.4)(solid-js@1.9.5)
'@tanstack/router-e2e-utils':
specifier: workspace:^
version: link:../../e2e-utils
@@ -2560,6 +2560,64 @@ importers:
specifier: ^5.1.4
version: 5.1.4(typescript@5.8.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))
+ e2e/solid-start/basic-auth:
+ dependencies:
+ '@prisma/client':
+ specifier: 5.22.0
+ version: 5.22.0(prisma@5.22.0)
+ '@tanstack/solid-router':
+ specifier: workspace:^
+ version: link:../../../packages/solid-router
+ '@tanstack/solid-router-devtools':
+ specifier: workspace:^
+ version: link:../../../packages/solid-router-devtools
+ '@tanstack/solid-start':
+ specifier: workspace:*
+ version: link:../../../packages/solid-start
+ prisma:
+ specifier: ^5.22.0
+ version: 5.22.0
+ redaxios:
+ specifier: ^0.5.1
+ version: 0.5.1
+ solid-js:
+ specifier: ^1.9.5
+ version: 1.9.5
+ tailwind-merge:
+ specifier: ^2.6.0
+ version: 2.6.0
+ vite:
+ specifier: ^7.1.7
+ version: 7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
+ devDependencies:
+ '@playwright/test':
+ specifier: ^1.52.0
+ version: 1.52.0
+ '@tanstack/router-e2e-utils':
+ specifier: workspace:^
+ version: link:../../e2e-utils
+ '@types/node':
+ specifier: 22.10.2
+ version: 22.10.2
+ autoprefixer:
+ specifier: ^10.4.20
+ version: 10.4.20(postcss@8.5.6)
+ postcss:
+ specifier: ^8.5.1
+ version: 8.5.6
+ tailwindcss:
+ specifier: ^3.4.17
+ version: 3.4.17
+ typescript:
+ specifier: ^5.7.2
+ version: 5.9.2
+ vite-plugin-solid:
+ specifier: ^2.11.9
+ version: 2.11.9(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))
+ vite-tsconfig-paths:
+ specifier: ^5.1.4
+ version: 5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))
+
e2e/solid-start/basic-solid-query:
dependencies:
'@tanstack/solid-query':
@@ -6986,6 +7044,58 @@ importers:
specifier: ^5.1.4
version: 5.1.4(typescript@5.8.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))
+ examples/solid/start-basic-auth:
+ dependencies:
+ '@prisma/client':
+ specifier: 5.22.0
+ version: 5.22.0(prisma@5.22.0)
+ '@tanstack/solid-router':
+ specifier: ^1.133.20
+ version: link:../../../packages/solid-router
+ '@tanstack/solid-router-devtools':
+ specifier: workspace:^
+ version: link:../../../packages/solid-router-devtools
+ '@tanstack/solid-start':
+ specifier: workspace:*
+ version: link:../../../packages/solid-start
+ prisma:
+ specifier: ^5.22.0
+ version: 5.22.0
+ redaxios:
+ specifier: ^0.5.1
+ version: 0.5.1
+ solid-js:
+ specifier: ^1.9.5
+ version: 1.9.5
+ tailwind-merge:
+ specifier: ^2.6.0
+ version: 2.6.0
+ devDependencies:
+ '@types/node':
+ specifier: 22.10.2
+ version: 22.10.2
+ autoprefixer:
+ specifier: ^10.4.20
+ version: 10.4.20(postcss@8.5.6)
+ postcss:
+ specifier: ^8.5.1
+ version: 8.5.6
+ tailwindcss:
+ specifier: ^3.4.17
+ version: 3.4.17
+ typescript:
+ specifier: ^5.7.2
+ version: 5.9.2
+ vite:
+ specifier: ^7.1.7
+ version: 7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)
+ vite-plugin-solid:
+ specifier: ^2.11.9
+ version: 2.11.9(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))
+ vite-tsconfig-paths:
+ specifier: ^5.1.4
+ version: 5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))
+
examples/solid/start-basic-static:
dependencies:
'@tanstack/solid-router':