Skip to content

Commit b3b8848

Browse files
committed
feat: migrate from Next.js 14 to Next.js 16
BREAKING CHANGE: Major version upgrade with significant changes ## Core Updates - Upgrade Next.js from 14.2.26 to 16.0.3 - Upgrade React from 18.2.0 to 19.x - Upgrade TypeScript from 5.1.6 to 5.6.x - Enable React Compiler for automatic memoization ## Authentication (NextAuth v4 → v5) - Migrate to NextAuth 5.0.0-beta.30 (Auth.js) - Create new auth.ts with handlers, auth, signIn, signOut exports - Update API route to use new handlers pattern - Update middleware to use auth() wrapper - Update utils/auth.ts to use new auth() function - Update type declarations for v5 compatibility ## Dependency Updates - Update Storybook from 7.5.3 to 8.4.x for React 19 support - Update embla-carousel-react from 7.1.0 to 8.5.x - Update apexcharts from 3.x to 4.x - Update eslint-config-next to 16.x - Remove deprecated @mantine/next package - Add babel-plugin-react-compiler ## Configuration Changes - Update next.config.js with reactCompiler option - Update tsconfig.json target to ES2017 - Add avatar property to User type ## Other Fixes - Handle Clerk authentication gracefully when not configured - Fix TypeScript errors related to Set iteration
1 parent 9a1f2b3 commit b3b8848

File tree

10 files changed

+20285
-85
lines changed

10 files changed

+20285
-85
lines changed
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import NextAuth from 'next-auth';
1+
import { handlers } from '@/auth';
22

3-
import { authOptions } from '@/app/lib/authOptions';
4-
5-
const handler = NextAuth(authOptions);
6-
7-
export { handler as GET, handler as POST };
3+
export const { GET, POST } = handlers;

app/auth/clerk/layout.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import { ReactNode } from 'react';
44

55
import { ClerkProvider } from '@clerk/nextjs';
6+
import { Alert, Container, Stack, Text, Title } from '@mantine/core';
7+
import { IconAlertCircle } from '@tabler/icons-react';
68

79
import { MainLayout } from '@/layouts/Main';
810
import { Providers } from '@/providers/session';
@@ -14,6 +16,50 @@ type AuthProps = {
1416
};
1517

1618
function AuthLayout({ children }: AuthProps) {
19+
// Check if Clerk is configured
20+
const clerkPublishableKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
21+
22+
if (!clerkPublishableKey) {
23+
return (
24+
<MainLayout>
25+
<Providers>
26+
<Container size="sm" py="xl">
27+
<Stack>
28+
<Alert
29+
icon={<IconAlertCircle size={16} />}
30+
title="Clerk Not Configured"
31+
color="yellow"
32+
>
33+
<Text size="sm">
34+
Clerk authentication is not configured. Please set the{' '}
35+
<code>NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY</code> environment
36+
variable to enable Clerk features.
37+
</Text>
38+
</Alert>
39+
<Title order={4}>To configure Clerk:</Title>
40+
<Text size="sm">
41+
1. Create an account at{' '}
42+
<a
43+
href="https://dashboard.clerk.com"
44+
target="_blank"
45+
rel="noopener noreferrer"
46+
>
47+
dashboard.clerk.com
48+
</a>
49+
</Text>
50+
<Text size="sm">
51+
2. Get your publishable key from the API Keys section
52+
</Text>
53+
<Text size="sm">
54+
3. Add it to your <code>.env.local</code> file
55+
</Text>
56+
</Stack>
57+
</Container>
58+
</Providers>
59+
</MainLayout>
60+
);
61+
}
62+
1763
return (
1864
<MainLayout>
1965
<Providers>

app/lib/authOptions.ts renamed to auth.ts

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import NextAuth from 'next-auth';
2-
import { NextAuthOptions } from 'next-auth';
3-
import { JWT } from 'next-auth/jwt';
4-
import CredentialsProvider from 'next-auth/providers/credentials';
2+
import Credentials from 'next-auth/providers/credentials';
3+
4+
import type { JWT } from 'next-auth/jwt';
55

66
// Helper function to refresh the token
77
async function refreshAccessToken(token: JWT) {
@@ -76,7 +76,7 @@ async function refreshAccessToken(token: JWT) {
7676
const newToken = refreshedTokens.token || refreshedTokens.accessToken;
7777

7878
// Update permissions from the new token if needed
79-
let permissions = [];
79+
let permissions: string[] = [];
8080
if (newToken) {
8181
try {
8282
const payload = JSON.parse(
@@ -107,9 +107,9 @@ async function refreshAccessToken(token: JWT) {
107107
}
108108
}
109109

110-
export const authOptions: NextAuthOptions = {
110+
export const { handlers, signIn, signOut, auth } = NextAuth({
111111
providers: [
112-
CredentialsProvider({
112+
Credentials({
113113
name: 'Credentials',
114114
credentials: {
115115
email: { label: 'Email', type: 'email' },
@@ -154,7 +154,7 @@ export const authOptions: NextAuthOptions = {
154154
// You can extract the payload portion without validating the signature
155155
// (NextAuth will handle token validation)
156156
const token = response.token;
157-
let permissions = [];
157+
let permissions: string[] = [];
158158

159159
if (token) {
160160
try {
@@ -211,7 +211,7 @@ export const authOptions: NextAuthOptions = {
211211
}
212212

213213
// Return the previous token if the access token has not expired yet
214-
if (token.expiration && new Date(token.expiration) > new Date()) {
214+
if (token.expiration && new Date(token.expiration as string) > new Date()) {
215215
console.log('Token not expired, returning existing token');
216216
return token;
217217
}
@@ -235,24 +235,18 @@ export const authOptions: NextAuthOptions = {
235235
session.user.id = token.id as string;
236236

237237
// Add custom fields to session
238-
// @ts-ignore - Adding custom properties
239-
session.accessToken = token.accessToken;
240-
// @ts-ignore
241-
session.roles = token.roles;
242-
// @ts-ignore
243-
session.permissions = token.permissions;
244-
// @ts-ignore
245-
session.expiration = token.expiration;
238+
session.accessToken = token.accessToken as string;
239+
session.roles = token.roles as string[];
240+
session.permissions = token.permissions as string[];
241+
session.expiration = token.expiration as string;
246242

247243
// Add refresh token error to session if it exists
248244
if (token.error) {
249-
// @ts-ignore
250-
session.error = token.error;
245+
session.error = token.error as string;
251246
}
252247
}
253248

254249
return session;
255250
},
256251
},
257-
secret: process.env.NEXTAUTH_SECRET!,
258-
};
252+
});

middleware.ts

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
import { clerkMiddleware } from '@clerk/nextjs/server';
21
import { NextResponse } from 'next/server';
3-
import { getToken } from 'next-auth/jwt';
42

3+
import { auth } from '@/auth';
54
import { PATH_DASHBOARD } from '@/routes';
65

7-
import type { NextRequest } from 'next/server';
8-
9-
export default clerkMiddleware();
10-
11-
export async function middleware(request: NextRequest) {
12-
const { pathname } = request.nextUrl;
6+
export default auth((req) => {
7+
const { pathname } = req.nextUrl;
8+
const isAuthenticated = !!req.auth;
139

1410
// Check if the path starts with these prefixes
1511
const isPublicPath =
@@ -25,27 +21,20 @@ export async function middleware(request: NextRequest) {
2521
pathname,
2622
);
2723

28-
const token = await getToken({
29-
req: request,
30-
secret: process.env.NEXTAUTH_SECRET,
31-
});
32-
33-
const isAuthenticated = !!token;
34-
3524
// Redirect authenticated users away from auth pages
3625
if (isAuthenticated && pathname.startsWith('/auth')) {
37-
return NextResponse.redirect(new URL(PATH_DASHBOARD.default, request.url));
26+
return NextResponse.redirect(new URL(PATH_DASHBOARD.default, req.url));
3827
}
3928

4029
// Redirect unauthenticated users to login page for protected routes
4130
if (!isAuthenticated && !isPublicPath) {
42-
const redirectUrl = new URL('/auth/signin', request.url);
43-
redirectUrl.searchParams.set('callbackUrl', encodeURI(request.url));
31+
const redirectUrl = new URL('/auth/signin', req.url);
32+
redirectUrl.searchParams.set('callbackUrl', encodeURI(req.url));
4433
return NextResponse.redirect(redirectUrl);
4534
}
4635

4736
return NextResponse.next();
48-
}
37+
});
4938

5039
// See "Matching Paths" below to learn more
5140
export const config = {

next.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
const nextConfig = {
33
reactStrictMode: true,
44
trailingSlash: false,
5+
6+
// Enable React Compiler (stable in Next.js 16)
7+
reactCompiler: true,
58
};
69

710
module.exports = nextConfig;

0 commit comments

Comments
 (0)