React SPA Patterns
SkillDev tools'React SPA auth patterns with @clerk/react for Vite/CRA - ClerkProvider
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the React SPA Patterns skill
What this skill tells your AI
The instructions your AI receives, as published by clerk/skills in skills/frameworks/clerk-react-patterns/SKILL.md and read by ahel’s review.
This skill covers
@clerk/reactfor Vite/CRA SPAs. For Next.js useclerk-nextjs-patterns. For TanStack Start useclerk-tanstack-patterns.
What Do You Need?
| Task | Reference |
|---|---|
| useAuth / useUser / useClerk hooks | references/hooks.md |
| Protected routes with React Router | references/protected-routes.md |
| Custom sign-in / sign-up forms | references/custom-flows.md |
| React Router v6/v7 integration | references/router-integration.md |
References
| Reference | Description |
|---|---|
references/hooks.md | useAuth, isLoaded guard |
references/protected-routes.md | ProtectedRoute pattern |
references/custom-flows.md | useSignIn, useSignUp flows |
references/router-integration.md | React Router v6/v7 setup |
Setup
npm install @clerk/react
.env:
VITE_CLERK_PUBLISHABLE_KEY=pk_...
src/main.tsx:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { ClerkProvider } from '@clerk/react'
import App from './App.tsx'
const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
createRoot(document.getElementById('root')!).render(
<StrictMode>
<ClerkProvider publishableKey={PUBLISHABLE_KEY}>
<App />
</ClerkProvider>
</StrictMode>,
)
Mental Model
@clerk/react is client-only — there is no server-side auth(). All auth state comes from hooks.
isLoadedmust betruebefore trustingisSignedIn— always guard onisLoadeduseClerk()gives access tosignOut,openSignIn,openUserProfileand other methodsgetToken()fromuseAuth()fetches the session JWT for API calls
Minimal Pattern
import { useAuth } from '@clerk/react'
export function Dashboard() {
const { isLoaded, isSignedIn, userId } = useAuth()
if (!isLoaded) return <div>Loading...</div>
if (!isSignedIn) return <div>Please sign in</div>
return <div>Hello {userId}</div>
}
Protected Route (React Router v6/v7)
import { Navigate, Outlet } from 'react-router-dom'
import { useAuth } from '@clerk/react'
export function ProtectedRoute() {
const { isLoaded, isSignedIn } = useAuth()
if (!isLoaded) return <div>Loading...</div>
if (!isSignedIn) return <Navigate to="/sign-in" replace />
return <Outlet />
}
<Routes>
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Route>
<Route path="/sign-in" element={<SignIn />} />
</Routes>
Token for API Calls
import { useAuth } from '@clerk/react'
export function DataFetcher() {
const { getToken } = useAuth()
async function fetchData() {
const token = await getToken()
if (!token) return
const res = await fetch('/api/data', {
headers: { Authorization: `Bearer ${token}` },
})
return res.json()
}
return <button onClick={fetchData}>Load</button>
}
Common Pitfalls
| Symptom | Cause | Fix |
|---|---|---|
isSignedIn is undefined | isLoaded is still false | Always check isLoaded first |
ClerkProvider missing | Provider not at root | Wrap <App> in main.tsx |
| Env var undefined | Wrong Vite prefix | Use VITE_CLERK_PUBLISHABLE_KEY, access via import.meta.env |
Token is null | User not signed in | Null-check getToken() result |
| Sign-in component shows blank | No publishableKey on provider | Pass publishableKey explicitly |
See Also
clerk-setup- Initial Clerk installclerk-custom-ui- Custom flows & appearanceclerk-orgs- B2B organizations
Docs
Signals
- GitHub stars
- 71
- Forks
- 4
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
clerk-react-patterns- Source
- github.com/clerk/skills