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 @@ -634,6 +634,10 @@
"label": "Kitchen Sink + Solid Query (code-based)",
"to": "framework/solid/examples/kitchen-sink-solid-query"
},
{
"label": "Authenticated Routes",
"to": "framework/solid/examples/authenticated-routes"
},
{
"label": "View Transitions",
"to": "framework/solid/examples/view-transitions"
Expand Down
8 changes: 8 additions & 0 deletions examples/solid/authenticated-routes-firebase/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Firebase Configuration: Copy this file to `.env.local` and add your Firebase credentials
# Obtain these values from your Firebase Console (see README.md for detailed setup instructions)
VITE_PUBLIC_FIREBASE_API_KEY=
VITE_PUBLIC_FIREBASE_AUTH_DOMAIN=
VITE_PUBLIC_FIREBASE_PROJECT_ID=
VITE_PUBLIC_FIREBASE_STORAGE_BUCKET=
VITE_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
VITE_PUBLIC_FIREBASE_APP_ID=
7 changes: 7 additions & 0 deletions examples/solid/authenticated-routes-firebase/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
.env.*
!.env.example
11 changes: 11 additions & 0 deletions examples/solid/authenticated-routes-firebase/.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
}
}
40 changes: 40 additions & 0 deletions examples/solid/authenticated-routes-firebase/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Firebase Setup

1. Create a [Firebase project](https://console.firebase.google.com/)
1. By default, firebase will configure an accepted domain for localhost...update if necessary!
2. Enable Authentication in the Firebase console
3. Add GitHub as an authentication provider:
- Go to **Authentication** > **Sign-in method** > **GitHub**
- Enable GitHub authentication
- You'll need to set up OAuth in your GitHub account:
- Go to [GitHub Developer Settings](https://github.com/settings/developers)
- Create a new OAuth app
- Set the homepage URL to your local or production URL
- Set the callback URL to: `https://your-firebase-project-id.firebaseapp.com/__/auth/handler`
- Copy the Client ID and Client Secret
- Return to Firebase console and paste the GitHub Client ID and Client Secret
- Save the changes

4. Create a web app in your Firebase project:
- Go to **Project Overview** > **Add app** > **Web**
- Register the app with a nickname
- Copy the Firebase configuration object for later use

## Setup .env.local

Copy the .env.example provided and configure with your firebase credentials

````VITE_FIREBASE_API_KEY=
VITE_FIREBASE_AUTH_DOMAIN=
VITE_FIREBASE_PROJECT_ID=
VITE_FIREBASE_STORAGE_BUCKET=
VITE_FIREBASE_MESSAGING_SENDER_ID=
VITE_FIREBASE_APP_ID=```
Comment on lines +27 to +32
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 | 🟠 Major

Fix the environment variable names in the README.

.env.example (and the code that consumes it) expects the VITE_PUBLIC_FIREBASE_* variables, but this snippet directs readers to configure VITE_FIREBASE_*. Following the current instructions leaves the actual env vars unset at runtime, so auth never initializes. Please align the README with the real variable names.

Apply this diff to update the snippet:

-```VITE_FIREBASE_API_KEY=
-VITE_FIREBASE_AUTH_DOMAIN=
-VITE_FIREBASE_PROJECT_ID=
-VITE_FIREBASE_STORAGE_BUCKET=
-VITE_FIREBASE_MESSAGING_SENDER_ID=
-VITE_FIREBASE_APP_ID=```
+```VITE_PUBLIC_FIREBASE_API_KEY=
+VITE_PUBLIC_FIREBASE_AUTH_DOMAIN=
+VITE_PUBLIC_FIREBASE_PROJECT_ID=
+VITE_PUBLIC_FIREBASE_STORAGE_BUCKET=
+VITE_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
+VITE_PUBLIC_FIREBASE_APP_ID=```
🤖 Prompt for AI Agents
In examples/solid/authenticated-routes-firebase/README.md around lines 27 to 32,
the README shows environment variable names prefixed with VITE_FIREBASE_* but
the project expects VITE_PUBLIC_FIREBASE_*; update the snippet to use
VITE_PUBLIC_FIREBASE_API_KEY, VITE_PUBLIC_FIREBASE_AUTH_DOMAIN,
VITE_PUBLIC_FIREBASE_PROJECT_ID, VITE_PUBLIC_FIREBASE_STORAGE_BUCKET,
VITE_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, and VITE_PUBLIC_FIREBASE_APP_ID so
they match .env.example and the code that consumes them.


## Run the app

To run this example:

- `npm install` or `yarn`
- `npm start` or `yarn start`
````
12 changes: 12 additions & 0 deletions examples/solid/authenticated-routes-firebase/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>
29 changes: 29 additions & 0 deletions examples/solid/authenticated-routes-firebase/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "tanstack-router-solid-example-authenticated-routes-firebase",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --port 3000",
"build": "vite build && tsc --noEmit",
"serve": "vite preview",
"start": "vite"
},
"dependencies": {
"@tailwindcss/postcss": "^4.1.15",
"@tanstack/solid-router": "^1.135.2",
"@tanstack/solid-router-devtools": "^1.135.2",
"@tanstack/router-plugin": "^1.135.2",
"firebase": "^11.4.0",
"postcss": "^8.5.1",
"solid-js": "^1.9.10",
"redaxios": "^0.5.1",
"simple-icons": "^14.9.0",
"tailwindcss": "^4.1.15",
"zod": "^3.24.2"
},
"devDependencies": {
"vite-plugin-solid": "^2.11.10",
"typescript": "^5.7.2",
"vite": "^7.1.7"
}
Comment on lines +11 to +28
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 | 🟠 Major

Use workspace:* for internal TanStack packages.

Per the repo guideline for package.json files, internal @tanstack/* deps should use the workspace protocol so the example stays in lockstep with the monorepo. Leaving the caret ranges pulls from npm instead of the local packages, risking mismatched or unreleased APIs.

As per coding guidelines

Apply this diff:

-    "@tanstack/solid-router": "^1.135.2",
-    "@tanstack/solid-router-devtools": "^1.135.2",
-    "@tanstack/router-plugin": "^1.135.2",
+    "@tanstack/solid-router": "workspace:*",
+    "@tanstack/solid-router-devtools": "workspace:*",
+    "@tanstack/router-plugin": "workspace:*",
📝 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
"dependencies": {
"@tailwindcss/postcss": "^4.1.15",
"@tanstack/solid-router": "^1.135.2",
"@tanstack/solid-router-devtools": "^1.135.2",
"@tanstack/router-plugin": "^1.135.2",
"firebase": "^11.4.0",
"postcss": "^8.5.1",
"solid-js": "^1.9.10",
"redaxios": "^0.5.1",
"simple-icons": "^14.9.0",
"tailwindcss": "^4.1.15",
"zod": "^3.24.2"
},
"devDependencies": {
"vite-plugin-solid": "^2.11.10",
"typescript": "^5.7.2",
"vite": "^7.1.7"
}
"dependencies": {
"@tailwindcss/postcss": "^4.1.15",
"@tanstack/solid-router": "workspace:*",
"@tanstack/solid-router-devtools": "workspace:*",
"@tanstack/router-plugin": "workspace:*",
"firebase": "^11.4.0",
"postcss": "^8.5.1",
"solid-js": "^1.9.10",
"redaxios": "^0.5.1",
"simple-icons": "^14.9.0",
"tailwindcss": "^4.1.15",
"zod": "^3.24.2"
},
"devDependencies": {
"vite-plugin-solid": "^2.11.10",
"typescript": "^5.7.2",
"vite": "^7.1.7"
}
🤖 Prompt for AI Agents
In examples/solid/authenticated-routes-firebase/package.json around lines 11 to
28, the internal @tanstack packages are using caret ranges which will pull from
npm instead of the monorepo; change the version specifiers for
"@tanstack/solid-router", "@tanstack/solid-router-devtools", and
"@tanstack/router-plugin" to use the workspace protocol (e.g. "workspace:*") so
the example resolves to the local packages and stays in lockstep with the repo;
ensure any other @tanstack/* entries in this file also use "workspace:*".

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
63 changes: 63 additions & 0 deletions examples/solid/authenticated-routes-firebase/src/auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import * as Solid from 'solid-js'

import { onAuthStateChanged, signInWithPopup, signOut } from 'firebase/auth'
import { flushSync } from 'react-dom'
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 React import from Solid.js application.

This is a Solid.js application, but it's importing flushSync from react-dom. This is incorrect and will not work as intended. In Solid.js, state updates are already synchronous and tracked, so you should remove this import and the flushSync wrapper calls.

Apply this diff:

 import * as Solid from 'solid-js'
 
 import { onAuthStateChanged, signInWithPopup, signOut } from 'firebase/auth'
-import { flushSync } from 'react-dom'
 import { auth } from './firebase/config'
 import type { AuthProvider, User } from 'firebase/auth'

Then remove the flushSync wrappers at lines 25-28 and 42-45.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In examples/solid/authenticated-routes-firebase/src/auth.tsx around line 4, the
file incorrectly imports flushSync from 'react-dom' and uses flushSync wrappers
at lines 25-28 and 42-45; remove the import line "import { flushSync } from
'react-dom'" and delete the flushSync(...) wrapper calls so the Solid.js state
updates use native Solid mechanisms (simply perform the state updates directly
without flushSync).

import { auth } from './firebase/config'
import type { AuthProvider, User } from 'firebase/auth'

export type AuthContextType = {
isAuthenticated: () => boolean
isInitialLoading: () => boolean
login: (provider: AuthProvider) => Promise<void>
logout: () => Promise<void>
user: () => User | null
}

const AuthContext = Solid.createContext<AuthContextType | null>(null)

export function AuthContextProvider(props: { children: Solid.JSX.Element }) {
const [user, setUser] = Solid.createSignal<User | null>(auth.currentUser)
const [isInitialLoading, setIsInitialLoading] = Solid.createSignal(true)
const isAuthenticated = () => !!user()

Solid.createEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
flushSync(() => {
setUser(user)
setIsInitialLoading(false)
})
})
return () => unsubscribe()
})

const logout = async () => {
console.log('Logging out...')
await signOut(auth)
setUser(null)
setIsInitialLoading(false)
}

const login = async (provider: AuthProvider) => {
const result = await signInWithPopup(auth, provider)
flushSync(() => {
setUser(result.user)
setIsInitialLoading(false)
})
}
Comment on lines +23 to +46
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 flushSync wrappers - not needed in Solid.js.

The flushSync calls at lines 25-28 and 42-45 are not needed in Solid.js. Solid.js state updates are already synchronous and tracked by the reactivity system. Simply call the setters directly.

Apply this diff:

   Solid.createEffect(() => {
     const unsubscribe = onAuthStateChanged(auth, (user) => {
-      flushSync(() => {
-        setUser(user)
-        setIsInitialLoading(false)
-      })
+      setUser(user)
+      setIsInitialLoading(false)
     })
     return () => unsubscribe()
   })

   // ...

   const login = async (provider: AuthProvider) => {
     const result = await signInWithPopup(auth, provider)
-    flushSync(() => {
-      setUser(result.user)
-      setIsInitialLoading(false)
-    })
+    setUser(result.user)
+    setIsInitialLoading(false)
   }
📝 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
Solid.createEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
flushSync(() => {
setUser(user)
setIsInitialLoading(false)
})
})
return () => unsubscribe()
})
const logout = async () => {
console.log('Logging out...')
await signOut(auth)
setUser(null)
setIsInitialLoading(false)
}
const login = async (provider: AuthProvider) => {
const result = await signInWithPopup(auth, provider)
flushSync(() => {
setUser(result.user)
setIsInitialLoading(false)
})
}
Solid.createEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setUser(user)
setIsInitialLoading(false)
})
return () => unsubscribe()
})
const logout = async () => {
console.log('Logging out...')
await signOut(auth)
setUser(null)
setIsInitialLoading(false)
}
const login = async (provider: AuthProvider) => {
const result = await signInWithPopup(auth, provider)
setUser(result.user)
setIsInitialLoading(false)
}
🤖 Prompt for AI Agents
In examples/solid/authenticated-routes-firebase/src/auth.tsx around lines 23 to
46, the review calls out unnecessary use of flushSync; remove the flushSync
wrappers around the state updates in both the onAuthStateChanged callback and
the login function and call setUser and setIsInitialLoading directly (i.e.,
replace flushSync(() => { setUser(...); setIsInitialLoading(...); }) with direct
setter calls), ensuring the unsubscribe return remains unchanged.


return (
<AuthContext.Provider
value={{ isInitialLoading, isAuthenticated, user, login, logout }}
>
{props.children}
</AuthContext.Provider>
)
}

export function useAuth() {
const context = Solid.useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be used within an AuthProvider')
}
return context
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Import the functions you need from the SDKs you need
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: import.meta.env.VITE_PUBLIC_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_PUBLIC_FIREBASE_APP_ID,
}

// Initialize Firebase
const app = initializeApp(firebaseConfig)
export const auth = getAuth(app)
51 changes: 51 additions & 0 deletions examples/solid/authenticated-routes-firebase/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { render } from 'solid-js/web'
import { RouterProvider, createRouter } from '@tanstack/solid-router'

import { routeTree } from './routeTree.gen'
import { AuthContextProvider, useAuth } from './auth'

import './styles.css'

// Set up a Router instance
const router = createRouter({
routeTree,
defaultPreload: 'intent',
scrollRestoration: true,
context: {
auth: undefined!, // This will be set after we wrap the app in AuthContextProvider
},
})

// Register things for typesafety
declare module '@tanstack/solid-router' {
interface Register {
router: typeof router
}
}

function InnerApp() {
const auth = useAuth()

// If the provider is initially loading, do not render the router
if (auth.isInitialLoading()) {
return (
<div class="flex h-screen w-full items-center justify-center p-4">
<div class="size-10 rounded-full border-4 border-gray-200 border-t-foreground animate-spin" />
</div>
)
}

return <RouterProvider router={router} context={{ auth }} />
}

function App() {
return (
<AuthContextProvider>
<InnerApp />
</AuthContextProvider>
)
}

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

render(() => <App />, rootElement)
50 changes: 50 additions & 0 deletions examples/solid/authenticated-routes-firebase/src/posts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import axios from 'redaxios'

async function loaderDelayFn<T>(fn: (...args: Array<any>) => Promise<T> | T) {
const delay = Number(sessionStorage.getItem('loaderDelay') ?? 0)
const delayPromise = new Promise((r) => setTimeout(r, delay))

await delayPromise
const res = await fn()

return res
}

type Invoice = {
id: number
title: string
body: string
}

let invoices: Array<Invoice> = null!

let invoicesPromise: Promise<void> | undefined = undefined

const ensureInvoices = async () => {
if (!invoicesPromise) {
invoicesPromise = Promise.resolve().then(async () => {
const { data } = await axios.get(
'https://jsonplaceholder.typicode.com/posts',
)
invoices = data.slice(0, 10)
})
}

await invoicesPromise
}

export async function fetchInvoices() {
return loaderDelayFn(() => ensureInvoices().then(() => invoices))
}

export async function fetchInvoiceById(id: number) {
return loaderDelayFn(() =>
ensureInvoices().then(() => {
const invoice = invoices.find((d) => d.id === id)
if (!invoice) {
throw new Error('Invoice not found')
}
return invoice
}),
)
}
Loading
Loading