-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: add start-neon-basic example with Neon Auth/Launchpad integration #5030
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
node_modules | ||
package-lock.json | ||
yarn.lock | ||
|
||
.DS_Store | ||
.cache | ||
.env | ||
.vercel | ||
.output | ||
.nitro | ||
/build/ | ||
/api/ | ||
/server/build | ||
/public/build# Sentry Config File | ||
.env.sentry-build-plugin | ||
/test-results/ | ||
/playwright-report/ | ||
/blob-report/ | ||
/playwright/.cache/ | ||
.tanstack |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
**/build | ||
**/public | ||
pnpm-lock.yaml | ||
routeTree.gen.ts |
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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
# TanStack Start + Neon Auth Example | ||
|
||
SSR-compatible authentication with Neon Auth and TanStack Start. | ||
|
||
- [TanStack Router Docs](https://tanstack.com/router) | ||
- [Neon Auth Documentation](https://neon.com/docs/neon-auth/overview) | ||
|
||
## Features | ||
|
||
- **Neon Auth Integration** - Complete authentication flow with Neon Auth (based on Stack Auth) | ||
- **SSR Compatible** - Works with TanStack Start's server-side rendering | ||
- **Auto Database Setup** - Neon Launchpad creates database connection | ||
- **Modern UI** - Clean interface with Tailwind CSS | ||
|
||
## Quickest (impatient) Start | ||
|
||
```bash | ||
npx gitpick TanStack/router/tree/main/examples/react/start-neon-basic start-neon-basic | ||
cd start-neon-basic | ||
npm install | ||
npm run dev | ||
``` | ||
|
||
## Quick Start | ||
|
||
1. **Install dependencies:** | ||
```bash | ||
pnpm install | ||
cp env.example .env | ||
``` | ||
|
||
2. **Get your Neon Auth credentials:** | ||
- [Neon Launchpad](https://neon.com/docs/reference/neon-launchpad) will automatically create a Neon project for you | ||
- Claim your project when prompted (a browser tab will open automatically, and the claim URL is also saved to .env) | ||
- Go to the "Auth" section in your project dashboard, enable Auth, and get your credentials | ||
- Edit `.env` and replace these values with your actual credentials: | ||
|
||
```bash | ||
VITE_STACK_PROJECT_ID=your_actual_project_id | ||
VITE_STACK_PUBLISHABLE_CLIENT_KEY=your_actual_publishable_key | ||
STACK_SECRET_SERVER_KEY=your_actual_secret_key | ||
``` | ||
|
||
3. **Run:** `pnpm dev` → Visit `http://localhost:3000` | ||
|
||
## Environment Variables | ||
|
||
- `VITE_STACK_PROJECT_ID` - Neon Auth project ID | ||
- `VITE_STACK_PUBLISHABLE_CLIENT_KEY` - Neon Auth publishable key | ||
- `STACK_SECRET_SERVER_KEY` - Neon Auth secret key (server-side only) | ||
|
||
### Database Auto-Creation | ||
|
||
This example uses the `@neondatabase/vite-plugin-postgres` plugin which automatically: | ||
- Creates a Neon database connection via [Neon Launchpad](https://neon.com/docs/reference/neon-launchpad) | ||
- Sets up the `DATABASE_URL` environment variable | ||
- Handles database initialization | ||
|
||
You can override this by setting your own `DATABASE_URL` in the `.env` file before running `pnpm dev`. | ||
|
||
## How It Works | ||
|
||
- **Auth Flow**: Login/Signup → Neon Auth → `/handler/*` callback → Redirect | ||
- **Handler Route**: `src/routes/handler.$.tsx` (client-only, catch-all) | ||
- **SSR Safe**: Uses `useState` + `useEffect` pattern | ||
|
||
## Project Structure | ||
|
||
``` | ||
src/ | ||
├── routes/ | ||
│ ├── __root.tsx # Root with StackProvider | ||
│ ├── handler.$.tsx # Auth callbacks (client-only) | ||
│ ├── index.tsx # Home page | ||
│ └── _authed/ # Protected routes | ||
├── stack.ts # Stack Auth configuration | ||
└── utils/ # Database utilities | ||
``` | ||
|
||
## Troubleshooting | ||
|
||
- **404 on `/handler/sign-in`**: Ensure file is named `handler.$.tsx` | ||
- **SSR errors**: All Stack Auth components must be client-only | ||
- **Route conflicts**: Delete `src/routeTree.gen.ts` and restart | ||
|
||
See [docs/AUTHENTICATION_TROUBLESHOOTING.md](./docs/AUTHENTICATION_TROUBLESHOOTING.md) for detailed solutions. |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,96 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Authentication Troubleshooting Guide | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Quick reference for Stack Auth + TanStack Start integration issues. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
## ⚠️ Critical Issues | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
### 1. Handler Route Naming | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
**❌ Wrong**: `handler.$splat.tsx`, `handler.$_.tsx`, `handler._.tsx` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
**✅ Correct**: `handler.$.tsx` (with `$` symbol only) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
### 2. SSR Compatibility | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
**❌ Wrong**: Render `StackHandler` during SSR | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
**✅ Correct**: Use client-only rendering with `useState` + `useEffect` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
### 3. Route Creation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
**❌ Wrong**: `createFileRoute("/handler/$")` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
**✅ Correct**: `createFileRoute()` (no arguments) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
## Working Solution | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
*Note: These are simplified examples. The actual code includes additional UI components, navigation, and features.* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
### Handler Route ([src/routes/handler.$.tsx](src/routes/handler.%24.tsx)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
```tsx | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { StackHandler } from "@stackframe/react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { stackClientApp } from "../stack"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useRouter, createFileRoute } from "@tanstack/react-router"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useState, useEffect } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
function HandlerComponent() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
const router = useRouter(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
const pathname = router.state.location.pathname; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
const [isClient, setIsClient] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
setIsClient(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!isClient) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return <div>Loading...</div>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
return <StackHandler app={stackClientApp} location={pathname} fullPage />; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
export const Route = createFileRoute()({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
component: HandlerComponent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
### Root Route ([src/routes/__root.tsx](/src/routes/__root.tsx) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Close the markdown link parenthesis in the “Root Route” heading. -### Root Route ([src/routes/__root.tsx](/src/routes/__root.tsx)
+### Root Route ([src/routes/__root.tsx](/src/routes/__root.tsx)) 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
```tsx | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { StackProvider, StackTheme } from "@stackframe/react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { stackClientApp } from "../stack"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
function RootComponent() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
<StackProvider app={stackClientApp}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
<StackTheme> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
<Outlet /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
</StackTheme> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
</StackProvider> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+53
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add missing Outlet import in the Root example. Without it, readers copying the snippet get a TS/runtime error. -import { StackProvider, StackTheme } from "@stackframe/react";
+import { StackProvider, StackTheme } from "@stackframe/react";
+import { Outlet } from "@tanstack/react-router";
import { stackClientApp } from "../stack"; 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
## Common Issues | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Issue | Solution | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|-------|----------| | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 404 on `/handler/sign-in` | Use `handler.$.tsx` naming, restart dev server | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `window is not defined` | Add client-only rendering to handler route | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Route tree errors | Delete `src/routeTree.gen.ts`, restart dev server | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TypeScript errors | Use `createFileRoute()` with no arguments | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
## Environment Variables | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
```env | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
VITE_STACK_PROJECT_ID=your_project_id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
VITE_STACK_PUBLISHABLE_CLIENT_KEY=your_publishable_key | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
STACK_SECRET_SERVER_KEY=your_secret_key | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
## Key Points | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
1. **Handler route must be client-only** - never render during SSR | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
2. **Use `handler.$.tsx`** - this is the only working catch-all naming | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
3. **Single handler file** - avoid duplicate route declarations | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
4. **Client-only auth UI** - use `useState` + `useEffect` pattern | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
## What NOT to Do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
- ❌ Use `handler.$splat.tsx`, `handler.$_.tsx`, or `handler._.tsx` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
- ❌ Use `createFileRoute("/handler/$")` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
- ❌ Render `StackHandler` during SSR | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
- ❌ Use Stack Auth hooks in SSR components | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
- ❌ Create multiple handler route files |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix broken link to handler route.
Use the actual file path with
$
instead of URL-encoding to%24
.📝 Committable suggestion
🤖 Prompt for AI Agents