Initial commit
This commit is contained in:
101
templates/nextjs/app-layout.tsx
Normal file
101
templates/nextjs/app-layout.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Next.js App Router Layout with Clerk
|
||||
*
|
||||
* Place this in app/layout.tsx
|
||||
*
|
||||
* Dependencies:
|
||||
* - @clerk/nextjs@^6.33.3
|
||||
*/
|
||||
|
||||
import { ClerkProvider } from '@clerk/nextjs'
|
||||
import { Inter } from 'next/font/google'
|
||||
import './globals.css'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
export const metadata = {
|
||||
title: 'My App',
|
||||
description: 'Authenticated with Clerk',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<ClerkProvider>
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</html>
|
||||
</ClerkProvider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* With Dark Mode Support (using next-themes):
|
||||
*
|
||||
* 1. Install: npm install next-themes
|
||||
* 2. Use this pattern:
|
||||
*/
|
||||
/*
|
||||
import { ClerkProvider } from '@clerk/nextjs'
|
||||
import { ThemeProvider } from 'next-themes'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<ClerkProvider>
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
</ClerkProvider>
|
||||
)
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* With Clerk Appearance Customization:
|
||||
*/
|
||||
/*
|
||||
import { ClerkProvider } from '@clerk/nextjs'
|
||||
import { dark } from '@clerk/themes'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<ClerkProvider
|
||||
appearance={{
|
||||
baseTheme: dark,
|
||||
variables: {
|
||||
colorPrimary: '#3b82f6',
|
||||
colorBackground: '#0f172a',
|
||||
},
|
||||
elements: {
|
||||
formButtonPrimary: 'bg-blue-500 hover:bg-blue-600',
|
||||
card: 'shadow-xl',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
</ClerkProvider>
|
||||
)
|
||||
}
|
||||
*/
|
||||
143
templates/nextjs/middleware.ts
Normal file
143
templates/nextjs/middleware.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Next.js Middleware with Clerk Authentication
|
||||
*
|
||||
* This middleware protects routes using Clerk's clerkMiddleware.
|
||||
* Place this file in the root of your Next.js project.
|
||||
*
|
||||
* Dependencies:
|
||||
* - @clerk/nextjs@^6.33.3
|
||||
*
|
||||
* CRITICAL (v6 Breaking Change):
|
||||
* - auth.protect() is now async - must use await
|
||||
* - Source: https://clerk.com/changelog/2024-10-22-clerk-nextjs-v6
|
||||
*/
|
||||
|
||||
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
|
||||
|
||||
/**
|
||||
* Define public routes (routes that don't require authentication)
|
||||
*
|
||||
* Glob patterns supported:
|
||||
* - '/path' - exact match
|
||||
* - '/path(.*)' - path and all sub-paths
|
||||
* - '/api/public/*' - wildcard
|
||||
*/
|
||||
const isPublicRoute = createRouteMatcher([
|
||||
'/', // Homepage
|
||||
'/sign-in(.*)', // Sign-in page and sub-paths
|
||||
'/sign-up(.*)', // Sign-up page and sub-paths
|
||||
'/api/public(.*)', // Public API routes
|
||||
'/api/webhooks(.*)', // Webhook endpoints
|
||||
'/about', // Static pages
|
||||
'/pricing',
|
||||
'/contact',
|
||||
])
|
||||
|
||||
/**
|
||||
* Alternative: Define protected routes instead
|
||||
*
|
||||
* Uncomment this pattern if you prefer to explicitly protect
|
||||
* specific routes rather than inverting the logic:
|
||||
*/
|
||||
/*
|
||||
const isProtectedRoute = createRouteMatcher([
|
||||
'/dashboard(.*)',
|
||||
'/profile(.*)',
|
||||
'/admin(.*)',
|
||||
'/api/private(.*)',
|
||||
])
|
||||
|
||||
export default clerkMiddleware(async (auth, request) => {
|
||||
if (isProtectedRoute(request)) {
|
||||
await auth.protect()
|
||||
}
|
||||
})
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default Pattern: Protect all routes except public ones
|
||||
*
|
||||
* CRITICAL:
|
||||
* - auth.protect() MUST be awaited (async in v6)
|
||||
* - Without await, route protection will not work
|
||||
*/
|
||||
export default clerkMiddleware(async (auth, request) => {
|
||||
if (!isPublicRoute(request)) {
|
||||
await auth.protect()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Matcher Configuration
|
||||
*
|
||||
* Defines which paths run middleware.
|
||||
* This is the recommended configuration from Clerk.
|
||||
*/
|
||||
export const config = {
|
||||
matcher: [
|
||||
// Skip Next.js internals and static files
|
||||
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
|
||||
|
||||
// Always run for API routes
|
||||
'/(api|trpc)(.*)',
|
||||
],
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced: Role-Based Protection
|
||||
*
|
||||
* Protect routes based on user role or organization membership:
|
||||
*/
|
||||
/*
|
||||
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
|
||||
|
||||
const isAdminRoute = createRouteMatcher(['/admin(.*)'])
|
||||
const isOrgRoute = createRouteMatcher(['/org(.*)'])
|
||||
|
||||
export default clerkMiddleware(async (auth, request) => {
|
||||
// Admin routes require 'admin' role
|
||||
if (isAdminRoute(request)) {
|
||||
await auth.protect((has) => {
|
||||
return has({ role: 'admin' })
|
||||
})
|
||||
}
|
||||
|
||||
// Organization routes require organization membership
|
||||
if (isOrgRoute(request)) {
|
||||
await auth.protect((has) => {
|
||||
return has({ permission: 'org:member' })
|
||||
})
|
||||
}
|
||||
|
||||
// All other routes use default protection
|
||||
if (!isPublicRoute(request)) {
|
||||
await auth.protect()
|
||||
}
|
||||
})
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
|
||||
'/(api|trpc)(.*)',
|
||||
],
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Troubleshooting:
|
||||
*
|
||||
* 1. Routes not protected?
|
||||
* - Ensure auth.protect() is awaited
|
||||
* - Check matcher configuration includes your routes
|
||||
* - Verify middleware.ts is in project root
|
||||
*
|
||||
* 2. Infinite redirects?
|
||||
* - Ensure sign-in/sign-up routes are in isPublicRoute
|
||||
* - Check NEXT_PUBLIC_CLERK_SIGN_IN_URL in .env.local
|
||||
*
|
||||
* 3. API routes returning HTML?
|
||||
* - Verify '/(api|trpc)(.*)' is in matcher
|
||||
* - Check API routes are not in isPublicRoute if protected
|
||||
*
|
||||
* Official Docs: https://clerk.com/docs/reference/nextjs/clerk-middleware
|
||||
*/
|
||||
114
templates/nextjs/server-component-example.tsx
Normal file
114
templates/nextjs/server-component-example.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Server Component with Clerk Auth
|
||||
*
|
||||
* Demonstrates using auth() and currentUser() in Server Components
|
||||
*
|
||||
* CRITICAL (v6): auth() is now async - must use await
|
||||
*/
|
||||
|
||||
import { auth, currentUser } from '@clerk/nextjs/server'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export default async function DashboardPage() {
|
||||
/**
|
||||
* Option 1: Lightweight auth check
|
||||
*
|
||||
* Use auth() when you only need userId/sessionId
|
||||
* This is faster than currentUser()
|
||||
*/
|
||||
const { userId, sessionId } = await auth()
|
||||
|
||||
// Redirect if not authenticated (shouldn't happen if middleware configured)
|
||||
if (!userId) {
|
||||
redirect('/sign-in')
|
||||
}
|
||||
|
||||
/**
|
||||
* Option 2: Full user object
|
||||
*
|
||||
* Use currentUser() when you need full user data
|
||||
* Heavier than auth(), so use sparingly
|
||||
*/
|
||||
const user = await currentUser()
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-8">
|
||||
<h1 className="text-3xl font-bold">Dashboard</h1>
|
||||
|
||||
<div className="mt-4 space-y-2">
|
||||
<p>
|
||||
<strong>User ID:</strong> {userId}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Session ID:</strong> {sessionId}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Email:</strong>{' '}
|
||||
{user?.primaryEmailAddress?.emailAddress}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Name:</strong> {user?.firstName} {user?.lastName}
|
||||
</p>
|
||||
|
||||
{/* Access public metadata */}
|
||||
{user?.publicMetadata && (
|
||||
<div>
|
||||
<strong>Role:</strong>{' '}
|
||||
{(user.publicMetadata as any).role || 'user'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* API Route Example (app/api/user/route.ts)
|
||||
*/
|
||||
/*
|
||||
import { auth, currentUser } from '@clerk/nextjs/server'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function GET() {
|
||||
const { userId } = await auth()
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const user = await currentUser()
|
||||
|
||||
return NextResponse.json({
|
||||
userId,
|
||||
email: user?.primaryEmailAddress?.emailAddress,
|
||||
name: `${user?.firstName} ${user?.lastName}`,
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Protected API Route with POST (app/api/items/route.ts)
|
||||
*/
|
||||
/*
|
||||
import { auth } from '@clerk/nextjs/server'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const { userId } = await auth()
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
|
||||
// Validate and process
|
||||
// Example: save to database with userId
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
itemId: crypto.randomUUID(),
|
||||
userId,
|
||||
}, { status: 201 })
|
||||
}
|
||||
*/
|
||||
Reference in New Issue
Block a user