next.config.ts 19 KB

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