Skip to content

Commit 95fbd03

Browse files
Refactor: Upgrade Next.js to 16.0.1 and Remove PPR (#153)
<!-- CURSOR_SUMMARY --> > [!NOTE] > Upgrades Next.js/React and build to Turbopack, removes docs (fumadocs), refactors sandboxes to a single tabbed page, renames middleware to proxy with route fixes, and updates tests/config. > > - **Core/Build** > - Upgrade `next` to `^16.0.1` and `react`/`react-dom` to `^19.2.0`. > - Use Turbopack for `build` and `preview` scripts. > - Enable `reactCompiler`; adjust `experimental` (`clientSegmentCache`), remove `ppr` and custom `webpack` `node-loader` rule. > - Trim `serverExternalPackages` to `['pino']`. > - TS: switch `jsx` to `react-jsx`; include `.next/dev/types`. > - **Routing/Middleware** > - Rename exported middleware to `proxy` in `src/proxy.ts`; update all usages/tests. > - Add `src/app/(rewrites)/[[...slug]]/not-found.tsx`. > - `route.ts`: normalize `/index` to `/` and adjust `generateStaticParams` slug mapping. > - **Dashboard – Sandboxes** > - Replace parallel routes/layout with single `page.tsx` using `DashboardTabs` and `Suspense`. > - Update `components`: `list-content` and `monitoring-content` props/types; charts/header imports/types aligned. > - **Docs Removal** > - Remove fumadocs packages and related UI/components (`src/features/docs/**`, `src/app/layout.config.tsx`). > - **Tests** > - Integration tests updated to call `proxy` instead of `middleware`. > - **UI/Misc** > - `AnimatedNumber`: `AnimatePresence` set `initial={false}`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 6b52beb. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent add202c commit 95fbd03

File tree

29 files changed

+801
-1246
lines changed

29 files changed

+801
-1246
lines changed

.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@ yarn-error.log*
3737
*.tsbuildinfo
3838
next-env.d.ts
3939

40-
# fuma-docs
41-
/.source
42-
4340
CLAUDE.md
4441

4542
# tooling

bun.lock

Lines changed: 672 additions & 624 deletions
Large diffs are not rendered by default.

next.config.mjs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,25 @@ export const DOCUMENTATION_DOMAIN = 'e2b.mintlify.app'
33

44
/** @type {import('next').NextConfig} */
55
const config = {
6-
eslint: {
7-
dirs: ['src', 'scripts'], // Only run ESLint on these directories during production builds
8-
},
96
reactStrictMode: true,
7+
reactCompiler: true,
108
experimental: {
11-
reactCompiler: true,
12-
ppr: true,
139
staleTimes: {
1410
dynamic: 180,
1511
static: 180,
1612
},
1713
serverActions: {
1814
bodySizeLimit: '5mb',
1915
},
16+
clientSegmentCache: true,
2017
},
2118
logging: {
2219
fetches: {
2320
fullUrl: true,
2421
},
2522
},
26-
serverExternalPackages: ['pino', 'pino-loki'],
23+
serverExternalPackages: ['pino'],
2724
trailingSlash: false,
28-
webpack: (config) => {
29-
config.module.rules.push({
30-
test: /\.node$/,
31-
use: 'node-loader',
32-
})
33-
34-
return config
35-
},
3625
headers: async () => [
3726
{
3827
source: '/(.*)',

package.json

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
"scripts": {
66
"<<<<<<< Next.js": "",
77
"dev": "bun scripts:check-app-env && next dev --turbopack | pino-pretty --colorize",
8-
"build": "next build",
8+
"build": "next build --turbopack",
99
"start": "next start",
10-
"preview": "next build && next start | pino-pretty --colorize",
10+
"preview": "next build --turbopack && next start | pino-pretty --colorize",
1111
"lint": "next lint",
1212
"lint:fix": "next lint --fix",
1313
"format": "prettier --write .",
@@ -39,7 +39,6 @@
3939
"test:ui:integration": "bun scripts:check-app-env && vitest --ui src/__test__/integration/"
4040
},
4141
"dependencies": {
42-
"@fumadocs/mdx-remote": "^1.2.0",
4342
"@google-cloud/storage": "^7.15.2",
4443
"@hookform/resolvers": "^5.2.2",
4544
"@next-safe-action/adapter-react-hook-form": "^2.0.0",
@@ -101,16 +100,13 @@
101100
"echarts": "^6.0.0",
102101
"echarts-for-react": "^3.0.2",
103102
"fast-xml-parser": "^4.5.1",
104-
"fumadocs-core": "^15.0.6",
105-
"fumadocs-mdx": "^11.5.3",
106-
"fumadocs-ui": "^15.0.6",
107103
"geist": "^1.3.1",
108104
"immer": "^10.1.1",
109105
"lucide-react": "^0.525.0",
110106
"micromatch": "^4.0.8",
111107
"motion": "^12.18.1",
112108
"nanoid": "^5.0.9",
113-
"next": "15.3.0-canary.23",
109+
"next": "^16.0.1",
114110
"next-safe-action": "^8.0.11",
115111
"next-themes": "^0.4.6",
116112
"nuqs": "^2.7.0",
@@ -119,9 +115,9 @@
119115
"pino": "^9.7.0",
120116
"postgres": "^3.4.5",
121117
"posthog-js": "^1.268.1",
122-
"react": "^19.1.0",
118+
"react": "^19.2.0",
123119
"react-day-picker": "^9.9.0",
124-
"react-dom": "^19.1.0",
120+
"react-dom": "^19.2.0",
125121
"react-error-boundary": "^5.0.0",
126122
"react-hook-form": "^7.66.0",
127123
"react-icons": "^5.4.0",
@@ -156,11 +152,10 @@
156152
"@testing-library/jest-dom": "^6.6.3",
157153
"@testing-library/react": "^16.2.0",
158154
"@types/bun": "^1.2.5",
159-
"pino-pretty": "^13.1.1",
160155
"@types/node": "22.10.10",
161156
"@types/pg": "^8.11.11",
162-
"@types/react": "^19.0.8",
163-
"@types/react-dom": "19.0.3",
157+
"@types/react": "^19.2.2",
158+
"@types/react-dom": "^19.2.2",
164159
"@types/semver": "^7.7.0",
165160
"@vitest/coverage-v8": "^3.0.7",
166161
"@vitest/ui": "3.0.7",
@@ -173,6 +168,7 @@
173168
"eslint-plugin-prettier": "^5.2.3",
174169
"node-loader": "^2.1.0",
175170
"openapi-typescript": "^7.8.0",
171+
"pino-pretty": "^13.1.1",
176172
"postcss": "8.5.1",
177173
"postcss-import": "^16.1.0",
178174
"prettier": "^3.4.2",

src/__test__/integration/middleware.test.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls'
33
import { kv } from '@/lib/clients/kv'
44
import { supabaseAdmin } from '@/lib/clients/supabase/admin'
55
import { checkUserTeamAuthorization } from '@/lib/utils/server'
6-
import { middleware } from '@/middleware'
6+
import { proxy } from '@/proxy'
77
import { createServerClient } from '@supabase/ssr'
88
import { NextRequest, NextResponse } from 'next/server'
99
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -166,7 +166,7 @@ describe('Middleware Integration Tests', () => {
166166
})
167167

168168
// Execute: Run the middleware with the unauthenticated request
169-
await middleware(request)
169+
await proxy(request)
170170

171171
// Verify: Check that a redirect to sign-in page occurred
172172
const redirectCalls = vi.mocked(NextResponse.redirect).mock.calls
@@ -208,8 +208,8 @@ describe('Middleware Integration Tests', () => {
208208
}) as unknown as ReturnType<typeof supabaseAdmin.from>
209209
)
210210

211-
// Execute: Run the middleware with the authenticated request
212-
await middleware(request)
211+
// Execute: Run the proxy with the authenticated request
212+
await proxy(request)
213213

214214
// Verify: Check that user is redirected to their default team
215215
const redirectCalls = vi.mocked(NextResponse.redirect).mock.calls
@@ -237,8 +237,8 @@ describe('Middleware Integration Tests', () => {
237237
// Setup: User has no access to the tampered team
238238
vi.mocked(checkUserTeamAuthorization).mockResolvedValue(false)
239239

240-
// Execute: Run the middleware with the tampered request
241-
await middleware(request)
240+
// Execute: Run the proxy with the tampered request
241+
await proxy(request)
242242

243243
// Verify: User is redirected away from the tampered team
244244
const redirectCalls = vi.mocked(NextResponse.redirect).mock.calls
@@ -268,8 +268,8 @@ describe('Middleware Integration Tests', () => {
268268
return Promise.resolve(null)
269269
})
270270

271-
// Execute: Run the middleware with the request
272-
await middleware(request)
271+
// Execute: Run the proxy with the request
272+
await proxy(request)
273273

274274
// Verify: Database check was skipped due to cache hit
275275
expect(checkUserTeamAuthorization).not.toHaveBeenCalled()
@@ -312,8 +312,8 @@ describe('Middleware Integration Tests', () => {
312312
}) as unknown as ReturnType<typeof supabaseAdmin.from>
313313
)
314314

315-
// Execute: Run the middleware with the request
316-
await middleware(request)
315+
// Execute: Run the proxy with the request
316+
await proxy(request)
317317

318318
// Verify: User is redirected to their default team
319319
const redirectCalls = vi.mocked(NextResponse.redirect).mock.calls
@@ -348,8 +348,8 @@ describe('Middleware Integration Tests', () => {
348348
}) as unknown as ReturnType<typeof supabaseAdmin.from>
349349
)
350350

351-
// Execute: Run the middleware with the request
352-
await middleware(request)
351+
// Execute: Run the proxy with the request
352+
await proxy(request)
353353

354354
// Verify: User is redirected to the new team page
355355
const redirectCalls = vi.mocked(NextResponse.redirect).mock.calls
@@ -388,8 +388,8 @@ describe('Middleware Integration Tests', () => {
388388
}) as unknown as ReturnType<typeof supabaseAdmin.from>
389389
)
390390

391-
// Execute: Run the middleware with the request
392-
await middleware(request)
391+
// Execute: Run the proxy with the request
392+
await proxy(request)
393393

394394
// Verify: User is redirected to home on error
395395
const redirectCalls = vi.mocked(NextResponse.redirect).mock.calls
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import NotFound from '@/ui/not-found'
2+
import { Metadata } from 'next'
3+
4+
export const metadata: Metadata = {
5+
title: '404 - Page Not Found',
6+
description:
7+
'The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.',
8+
robots: 'noindex, nofollow',
9+
}
10+
11+
export default function RootNotFound() {
12+
return <NotFound />
13+
}

src/app/(rewrites)/[[...slug]]/route.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,20 @@ const REVALIDATE_TIME = 900 // 15 minutes ttl
1818
export async function GET(request: NextRequest): Promise<Response> {
1919
const url = new URL(request.url)
2020

21+
// Next.js (versions > 15.3.0) can alias the root path ("/") in `request.url` to
22+
// the corresponding file path (e.g., "/index") during internal processing.
23+
// This is a hotfix to normalize the pathname back to "/". We previously tried
24+
// using `request.nextUrl.pathname` to preserve the original client-requested URL,
25+
// but that approach did not work as well in practice.
26+
// This issue does only seem to occur in Vercel Production builds, not in local development or preview deployments.
27+
28+
// NOTE - Not sure if this is a bug or intended from the Next.js team, but it is what it is.
29+
// We need to handle this case because @rewrites.ts has a config for rewriting "/" (index),
30+
// and without this normalization, we would get a 404 in production when Next.js aliases "/" to "/index".
31+
if (url.pathname === '/index') {
32+
url.pathname = '/'
33+
}
34+
2135
const requestHostname = url.hostname
2236

2337
const updateUrlHostname = (newHostname: string) => {
@@ -110,7 +124,7 @@ export async function generateStaticParams() {
110124
const url = new URL(entry.url)
111125
const pathname = url.pathname
112126

113-
// Check if this path matches any rule in ROUTE_REWRITE_CONFIG
127+
// check if this path matches any rule in ROUTE_REWRITE_CONFIG
114128
for (const domainConfig of ROUTE_REWRITE_CONFIG) {
115129
const isIndex = pathname === '/' || pathname === ''
116130
const matchingRule = domainConfig.rules.find((rule) => {
@@ -130,13 +144,14 @@ export async function generateStaticParams() {
130144
return false
131145
})
132146
.map((entry) => {
133-
// Map the filtered entries to slug format
147+
// map the filtered entries to slug format
134148
const url = new URL(entry.url)
135149
const pathname = url.pathname
136150
const pathSegments = pathname
137151
.split('/')
138152
.filter((segment) => segment !== '')
139-
return { slug: pathSegments.length > 0 ? pathSegments : undefined }
153+
154+
return { slug: pathSegments }
140155
})
141156

142157
return slugs

src/app/dashboard/[teamIdOrSlug]/sandboxes/@list/loading.tsx

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/app/dashboard/[teamIdOrSlug]/sandboxes/@list/page.tsx renamed to src/app/dashboard/[teamIdOrSlug]/sandboxes/components/list-content.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,13 @@ import { getTeamSandboxes } from '@/server/sandboxes/get-team-sandboxes'
44
import { getTeamSandboxesMetrics } from '@/server/sandboxes/get-team-sandboxes-metrics'
55
import ErrorBoundary from '@/ui/error'
66

7-
interface PageProps {
8-
params: Promise<{
9-
teamIdOrSlug: string
10-
}>
11-
}
12-
13-
export default async function Page({ params }: PageProps) {
14-
const { teamIdOrSlug } = await params
15-
16-
return <PageContent teamIdOrSlug={teamIdOrSlug} />
17-
}
18-
19-
interface PageContentProps {
7+
interface ListContentProps {
208
teamIdOrSlug: string
219
}
2210

23-
async function PageContent({ teamIdOrSlug }: PageContentProps) {
11+
export default async function ListContent({
12+
teamIdOrSlug,
13+
}: ListContentProps) {
2414
const teamId = await resolveTeamIdInServerComponent(teamIdOrSlug)
2515

2616
const sandboxesRes = await getTeamSandboxes({ teamId })
@@ -59,3 +49,4 @@ async function PageContent({ teamIdOrSlug }: PageContentProps) {
5949
</div>
6050
)
6151
}
52+

src/app/dashboard/[teamIdOrSlug]/sandboxes/@monitoring/page.tsx renamed to src/app/dashboard/[teamIdOrSlug]/sandboxes/components/monitoring-content.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
import { TeamMetricsCharts } from '@/features/dashboard/sandboxes/monitoring/charts/charts'
22
import SandboxesMonitoringHeader from '@/features/dashboard/sandboxes/monitoring/header'
33

4-
export interface SandboxesMonitoringPageParams {
4+
export interface MonitoringContentParams {
55
teamIdOrSlug: string
66
}
77

8-
export interface SandboxesMonitoringPageSearchParams {
8+
export interface MonitoringContentSearchParams {
99
start?: string
1010
end?: string
1111
}
1212

13-
interface SandboxesMonitoringPageProps {
14-
params: Promise<SandboxesMonitoringPageParams>
15-
searchParams: Promise<SandboxesMonitoringPageSearchParams>
13+
interface MonitoringContentProps {
14+
params: Promise<MonitoringContentParams>
15+
searchParams: Promise<MonitoringContentSearchParams>
1616
}
1717

18-
export default function SandboxesMonitoringPage({
18+
export default function MonitoringContent({
1919
params,
2020
searchParams,
21-
}: SandboxesMonitoringPageProps) {
21+
}: MonitoringContentProps) {
2222
return (
2323
<div className="flex flex-col h-full relative min-h-0 max-md:overflow-y-auto">
2424
<SandboxesMonitoringHeader params={params} />
@@ -28,3 +28,4 @@ export default function SandboxesMonitoringPage({
2828
</div>
2929
)
3030
}
31+

0 commit comments

Comments
 (0)