next.config.ts667 lines · main
1/* eslint-disable no-restricted-exports */
2
3import bundleAnalyzer from '@next/bundle-analyzer'
4import { withSentryConfig } from '@sentry/nextjs'
5import type { NextConfig } from 'next'
6
7import { getCSP } from './csp'
8
9const withBundleAnalyzer = bundleAnalyzer({
10 enabled: process.env.ANALYZE === 'true',
11})
12
13function getAssetPrefix() {
14 // If not force enabled, but not production env, disable CDN
15 if (process.env.FORCE_ASSET_CDN !== '1' && process.env.VERCEL_ENV !== 'production') {
16 return undefined
17 }
18
19 // Force disable CDN
20 if (process.env.FORCE_ASSET_CDN === '-1') {
21 return undefined
22 }
23
24 const BRIVEN_ASSETS_URL =
25 process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging'
26 ? 'https://frontend-assets.briven.green'
27 : 'https://frontend-assets.supabase.com'
28
29 return `${BRIVEN_ASSETS_URL}/${process.env.SITE_NAME}/${process.env.VERCEL_GIT_COMMIT_SHA?.substring(0, 12) ?? 'unknown'}`
30}
31
32const marketplaceApiUrl = process.env.NEXT_PUBLIC_MARKETPLACE_API_URL
33 ? new URL(process.env.NEXT_PUBLIC_MARKETPLACE_API_URL)
34 : null
35
36const marketplaceApiProtocol: 'http' | 'https' | null =
37 marketplaceApiUrl?.protocol === 'https:'
38 ? 'https'
39 : marketplaceApiUrl?.protocol === 'http:'
40 ? 'http'
41 : null
42
43// Use `satisfies` instead of `: NextConfig` so TypeScript preserves narrow
44// inferred types (e.g. async headers → Promise). This avoids TS2345 when
45// wrapper functions (bundle-analyzer, sentry) resolve their `next` peer
46// types to a different major version than studio's own next dependency.
47const nextConfig = {
48 basePath: process.env.NEXT_PUBLIC_BASE_PATH,
49 assetPrefix: getAssetPrefix(),
50 output: 'standalone',
51 experimental: {
52 clientRouterFilter: false,
53 },
54 async rewrites() {
55 return [
56 {
57 source: `/.well-known/vercel/flags`,
58 destination: `https://supabase.com/.well-known/vercel/flags`,
59 basePath: false as const,
60 },
61 ]
62 },
63 async redirects() {
64 return [
65 ...(process.env.NEXT_PUBLIC_IS_PLATFORM === 'true'
66 ? [
67 {
68 source: '/',
69 has: [
70 {
71 type: 'query' as const,
72 key: 'next',
73 value: 'new-project',
74 },
75 ],
76 destination: '/new/new-project',
77 permanent: false,
78 },
79 {
80 source: '/',
81 destination: '/org',
82 permanent: false,
83 },
84 {
85 source: '/register',
86 destination: '/sign-up',
87 permanent: false,
88 },
89 {
90 source: '/signup',
91 destination: '/sign-up',
92 permanent: false,
93 },
94 {
95 source: '/signin',
96 destination: '/sign-in',
97 permanent: false,
98 },
99 {
100 source: '/login',
101 destination: '/sign-in',
102 permanent: false,
103 },
104 {
105 source: '/log-in',
106 destination: '/sign-in',
107 permanent: false,
108 },
109 {
110 source: '/project/:ref/building',
111 destination: '/project/:ref',
112 permanent: false,
113 },
114 ]
115 : [
116 {
117 source: '/',
118 destination: '/project/default',
119 permanent: false,
120 },
121 {
122 source: '/register',
123 destination: '/project/default',
124 permanent: false,
125 },
126 {
127 source: '/signup',
128 destination: '/project/default',
129 permanent: false,
130 },
131 {
132 source: '/signin',
133 destination: '/project/default',
134 permanent: false,
135 },
136 {
137 source: '/login',
138 destination: '/project/default',
139 permanent: false,
140 },
141 {
142 source: '/log-in',
143 destination: '/project/default',
144 permanent: false,
145 },
146 {
147 source: '/project/:ref/building',
148 destination: '/project/:ref',
149 permanent: false,
150 },
151 ]),
152 {
153 source: '/project/:ref/auth',
154 destination: '/project/:ref/auth/users',
155 permanent: true,
156 },
157 {
158 source: '/project/:ref/auth/advanced',
159 destination: '/project/:ref/auth/performance',
160 permanent: true,
161 },
162 {
163 source: '/project/:ref/database',
164 destination: '/project/:ref/database/tables',
165 permanent: true,
166 },
167 {
168 source: '/project/:ref/database/graphiql',
169 destination: '/project/:ref/api/graphiql',
170 permanent: true,
171 },
172 {
173 source: '/project/:ref/storage',
174 destination: '/project/:ref/storage/files',
175 permanent: true,
176 },
177 {
178 source: '/project/:ref/storage/buckets',
179 destination: '/project/:ref/storage/files',
180 permanent: true,
181 },
182 {
183 source: '/project/:ref/storage/policies',
184 destination: '/project/:ref/storage/files/policies',
185 permanent: true,
186 },
187 {
188 source: '/project/:ref/storage/buckets/:bucketId',
189 destination: '/project/:ref/storage/files/buckets/:bucketId',
190 permanent: true,
191 },
192 {
193 permanent: true,
194 source: '/project/:ref/settings/api-keys/new',
195 destination: '/project/:ref/settings/api-keys',
196 },
197 {
198 source: '/project/:ref/settings/storage',
199 destination: '/project/:ref/storage/files/settings',
200 permanent: true,
201 },
202 {
203 source: '/project/:ref/storage/settings',
204 destination: '/project/:ref/storage/files/settings',
205 permanent: true,
206 },
207 {
208 source: '/project/:ref/settings/database',
209 destination: '/project/:ref/database/settings',
210 permanent: true,
211 },
212 {
213 source: '/project/:ref/settings',
214 destination: '/project/:ref/settings/general',
215 permanent: true,
216 },
217 {
218 source: '/project/:ref/auth/settings',
219 destination: '/project/:ref/auth/users',
220 permanent: true,
221 },
222 {
223 source: '/project/:ref/settings/billing/subscription',
224 has: [
225 {
226 type: 'query',
227 key: 'panel',
228 value: 'subscriptionPlan',
229 },
230 ],
231 destination: '/org/_/billing?panel=subscriptionPlan',
232 permanent: true,
233 },
234 {
235 source: '/project/:ref/settings/billing/subscription',
236 has: [
237 {
238 type: 'query',
239 key: 'panel',
240 value: 'pitr',
241 },
242 ],
243 destination: '/project/:ref/settings/addons?panel=pitr',
244 permanent: true,
245 },
246 {
247 source: '/project/:ref/settings/billing/subscription',
248 has: [
249 {
250 type: 'query',
251 key: 'panel',
252 value: 'computeInstance',
253 },
254 ],
255 destination: '/project/:ref/settings/compute-and-disk',
256 permanent: true,
257 },
258 {
259 source: '/project/:ref/settings/billing/subscription',
260 has: [
261 {
262 type: 'query',
263 key: 'panel',
264 value: 'customDomain',
265 },
266 ],
267 destination: '/project/:ref/settings/addons?panel=customDomain',
268 permanent: true,
269 },
270 {
271 source: '/project/:ref/settings/billing/subscription',
272 destination: '/org/_/billing',
273 permanent: true,
274 },
275 {
276 permanent: true,
277 source: '/project/:ref/settings/jwt/signing-keys',
278 destination: '/project/:ref/settings/jwt',
279 },
280 {
281 source: '/project/:ref/database/api-logs',
282 destination: '/project/:ref/logs/edge-logs',
283 permanent: true,
284 },
285 {
286 source: '/project/:ref/database/postgres-logs',
287 destination: '/project/:ref/logs/postgres-logs',
288 permanent: true,
289 },
290 {
291 source: '/project/:ref/database/postgrest-logs',
292 destination: '/project/:ref/logs/postgrest-logs',
293 permanent: true,
294 },
295 {
296 source: '/project/:ref/database/pgbouncer-logs',
297 destination: '/project/:ref/logs/pooler-logs',
298 permanent: true,
299 },
300 {
301 source: '/project/:ref/logs/pgbouncer-logs',
302 destination: '/project/:ref/logs/pooler-logs',
303 permanent: true,
304 },
305 {
306 source: '/project/:ref/database/realtime-logs',
307 destination: '/project/:ref/logs/realtime-logs',
308 permanent: true,
309 },
310 {
311 source: '/project/:ref/storage/logs',
312 destination: '/project/:ref/logs/storage-logs',
313 permanent: true,
314 },
315 {
316 source: '/project/:ref/auth/logs',
317 destination: '/project/:ref/logs/auth-logs',
318 permanent: true,
319 },
320 {
321 source: '/project/:ref/logs-explorer',
322 destination: '/project/:ref/logs/explorer',
323 permanent: true,
324 },
325 {
326 source: '/project/:ref/sql/templates',
327 destination: '/project/:ref/sql',
328 permanent: true,
329 },
330 {
331 source: '/org/:slug/settings',
332 destination: '/org/:slug/general',
333 permanent: true,
334 },
335 {
336 source: '/project/:ref/settings/billing/update',
337 destination: '/org/_/billing',
338 permanent: true,
339 },
340 {
341 source: '/project/:ref/settings/billing/update/free',
342 destination: '/org/_/billing',
343 permanent: true,
344 },
345 {
346 source: '/project/:ref/settings/billing/update/pro',
347 destination: '/org/_/billing',
348 permanent: true,
349 },
350 {
351 source: '/project/:ref/settings/billing/update/team',
352 destination: '/org/_/billing',
353 permanent: true,
354 },
355 {
356 source: '/project/:ref/settings/billing/update/enterprise',
357 destination: '/org/_/billing',
358 permanent: true,
359 },
360 {
361 permanent: true,
362 source: '/project/:ref/reports/linter',
363 destination: '/project/:ref/database/linter',
364 },
365 {
366 permanent: true,
367 source: '/project/:ref/reports',
368 destination: '/project/:ref/observability',
369 },
370 {
371 permanent: true,
372 source: '/project/:ref/reports/:path*',
373 destination: '/project/:ref/observability/:path*',
374 },
375 {
376 permanent: true,
377 source: '/project/:ref/query-performance',
378 destination: '/project/:ref/observability/query-performance',
379 },
380 {
381 permanent: true,
382 source: '/project/:ref/advisors/query-performance',
383 destination: '/project/:ref/observability/query-performance',
384 },
385 {
386 permanent: true,
387 source: '/project/:ref/database/query-performance',
388 destination: '/project/:ref/observability/query-performance',
389 },
390 {
391 permanent: true,
392 source: '/project/:ref/auth/column-privileges',
393 destination: '/project/:ref/database/column-privileges',
394 },
395 {
396 permanent: true,
397 source: '/project/:ref/database/linter',
398 destination: '/project/:ref/database/security-advisor',
399 },
400 {
401 permanent: true,
402 source: '/project/:ref/database/security-advisor',
403 destination: '/project/:ref/advisors/security',
404 },
405 {
406 permanent: true,
407 source: '/project/:ref/database/performance-advisor',
408 destination: '/project/:ref/advisors/performance',
409 },
410 {
411 permanent: true,
412 source: '/project/:ref/database/webhooks',
413 destination: '/project/:ref/integrations/webhooks/overview',
414 },
415 {
416 permanent: true,
417 source: '/project/:ref/database/wrappers',
418 destination: '/project/:ref/integrations?category=wrapper',
419 },
420 {
421 permanent: true,
422 source: '/project/:ref/database/cron-jobs',
423 destination: '/project/:ref/integrations/cron',
424 },
425 {
426 permanent: true,
427 source: '/project/:ref/api/graphiql',
428 destination: '/project/:ref/integrations/graphiql',
429 },
430 {
431 permanent: true,
432 source: '/project/:ref/settings/vault/secrets',
433 destination: '/project/:ref/integrations/vault/secrets',
434 },
435 {
436 permanent: true,
437 source: '/project/:ref/settings/vault/keys',
438 destination: '/project/:ref/integrations/vault/keys',
439 },
440 {
441 permanent: true,
442 source: '/project/:ref/integrations/cron-jobs',
443 destination: '/project/:ref/integrations/cron',
444 },
445 {
446 permanent: true,
447 source: '/project/:ref/settings/warehouse',
448 destination: '/project/:ref/settings/general',
449 },
450 {
451 permanent: true,
452 source: '/project/:ref/settings/functions',
453 destination: '/project/:ref/functions/secrets',
454 },
455 {
456 source: '/org/:slug/invoices',
457 destination: '/org/:slug/billing#invoices',
458 permanent: true,
459 },
460 {
461 source: '/projects',
462 destination: '/organizations',
463 permanent: false,
464 },
465 {
466 source: '/project/:ref/settings/auth',
467 destination: '/project/:ref/auth/providers',
468 permanent: true,
469 },
470 {
471 source: '/project/:ref/settings/api',
472 destination: '/project/:ref/integrations/data_api/overview',
473 permanent: false,
474 },
475 {
476 source: '/project/:ref/api',
477 destination: '/project/:ref/integrations/data_api/docs',
478 permanent: false,
479 },
480
481 ...(process.env.NEXT_PUBLIC_BASE_PATH?.length
482 ? [
483 {
484 source: '/',
485 destination: process.env.NEXT_PUBLIC_BASE_PATH,
486 basePath: false as const,
487 permanent: false,
488 },
489 ]
490 : []),
491
492 ...(process.env.MAINTENANCE_MODE === 'true'
493 ? [
494 {
495 source: '/((?!maintenance|img).*)', // Redirect all paths except /maintenance and /img
496 destination: '/maintenance',
497 permanent: false,
498 },
499 ]
500 : [
501 {
502 source: '/maintenance',
503 destination: '/',
504 permanent: false,
505 },
506 ]),
507 ]
508 },
509 async headers() {
510 return [
511 {
512 source: '/(.*?)',
513 headers: [
514 {
515 key: 'X-Frame-Options',
516 value: 'DENY',
517 },
518 {
519 key: 'X-Content-Type-Options',
520 value: 'no-sniff',
521 },
522 {
523 key: 'Strict-Transport-Security',
524 value:
525 process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' && process.env.VERCEL === '1'
526 ? 'max-age=31536000; includeSubDomains; preload'
527 : '',
528 },
529 {
530 key: 'Content-Security-Policy',
531 value:
532 process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' ? getCSP() : "frame-ancestors 'none';",
533 },
534 {
535 key: 'Referrer-Policy',
536 value: 'strict-origin-when-cross-origin',
537 },
538 ],
539 },
540 {
541 source: '/.well-known/vercel/flags',
542 headers: [
543 {
544 key: 'content-type',
545 value: 'application/json',
546 },
547 ],
548 },
549 {
550 source: '/img/:slug*',
551 headers: [{ key: 'cache-control', value: 'public, max-age=2592000' }],
552 },
553 {
554 source: '/favicon/:slug*',
555 headers: [{ key: 'cache-control', value: 'public, max-age=86400' }],
556 },
557 {
558 source: '/(.*).ts',
559 headers: [{ key: 'content-type', value: 'text/typescript' }],
560 },
561 ]
562 },
563 images: {
564 dangerouslyAllowSVG: false,
565 remotePatterns: [
566 {
567 protocol: 'https',
568 hostname: 'github.com',
569 port: '',
570 pathname: '**',
571 },
572 {
573 protocol: 'https',
574 hostname: 'avatars.githubusercontent.com',
575 port: '',
576 pathname: '/u/*',
577 },
578 {
579 protocol: 'https',
580 hostname: 'api-frameworks.vercel.sh',
581 port: '',
582 pathname: '**',
583 },
584 {
585 protocol: 'https',
586 hostname: 'vercel.com',
587 port: '',
588 pathname: '**',
589 },
590 ...(marketplaceApiUrl
591 ? [
592 {
593 ...(marketplaceApiProtocol ? { protocol: marketplaceApiProtocol } : {}),
594 hostname: marketplaceApiUrl.hostname,
595 port: marketplaceApiUrl.port,
596 pathname: '**',
597 },
598 ]
599 : []),
600 ],
601 },
602 transpilePackages: ['ui', 'ui-patterns', 'common', 'shared-data', 'api-types', 'icons'],
603 serverExternalPackages: ['libpg-query'],
604 turbopack: {
605 rules: {
606 '*.md': {
607 loaders: ['raw-loader'],
608 as: '*.js',
609 },
610 // special case for Deno libs to be loaded as a raw text. They're passed as raw text to the Monaco editor.
611 'edge-runtime.d.ts': {
612 loaders: ['raw-loader'],
613 as: '*.js',
614 },
615 'lib.deno.d.ts': {
616 loaders: ['raw-loader'],
617 as: '*.js',
618 },
619 },
620 },
621 onDemandEntries: {
622 maxInactiveAge: 24 * 60 * 60 * 1000,
623 pagesBufferLength: 100,
624 },
625 typescript: {
626 // Typechecking is run via GitHub Action only for efficiency
627 // For production, we run typechecks separate from the build command (pnpm typecheck && pnpm build)
628 ignoreBuildErrors: true,
629 },
630} satisfies NextConfig
631
632// Make sure adding Sentry options is the last code to run before exporting, to
633// ensure that your source maps include changes from all other Webpack plugins
634const platformConfig =
635 process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' ? withBundleAnalyzer(nextConfig) : nextConfig
636
637export default process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' && process.env.VERCEL === '1'
638 ? withSentryConfig(platformConfig, {
639 silent: true,
640
641 // For all available options, see:
642 // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
643
644 // Upload a larger set of source maps for prettier stack traces (increases build time)
645 widenClientFileUpload: true,
646
647 // Automatically annotate React components to show their full name in breadcrumbs and session replay
648 reactComponentAnnotation: {
649 enabled: true,
650 },
651
652 // Automatically tree-shake Sentry logger statements to reduce bundle size
653 disableLogger: true,
654
655 // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
656 // See the following for more information:
657 // https://docs.sentry.io/product/crons/
658 // https://vercel.com/docs/cron-jobs
659 automaticVercelMonitors: true,
660
661 // Annotate bundles at build time so thirdPartyErrorFilterIntegration can
662 // distinguish our code from browser extensions / injected scripts at runtime.
663 unstable_sentryWebpackPluginOptions: {
664 applicationKey: 'briven-studio',
665 },
666 })
667 : platformConfig