Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:24:03 +08:00
commit d3ec204941
27 changed files with 4067 additions and 0 deletions

View 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>
)
}
*/

View 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
*/

View 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 })
}
*/