next.config.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. /**
  2. * == Notes for production build==
  3. * The modules required from this file must be transpiled before running `next build`.
  4. *
  5. * See: https://github.com/vercel/next.js/discussions/35969#discussioncomment-2522954
  6. */
  7. const path = require('node:path');
  8. const {
  9. PHASE_PRODUCTION_BUILD,
  10. PHASE_PRODUCTION_SERVER,
  11. } = require('next/constants');
  12. const getTranspilePackages = () => {
  13. const { listPrefixedPackages } = require('./src/utils/next.config.utils');
  14. const packages = [
  15. // listing ESM packages until experimental.esmExternals works correctly to avoid ERR_REQUIRE_ESM
  16. 'react-markdown',
  17. 'unified',
  18. 'markdown-table',
  19. 'bail',
  20. 'ccount',
  21. 'character-entities',
  22. 'character-entities-html4',
  23. 'character-entities-legacy',
  24. 'comma-separated-tokens',
  25. 'decode-named-character-reference',
  26. 'devlop',
  27. 'fault',
  28. 'escape-string-regexp',
  29. 'hastscript',
  30. 'html-void-elements',
  31. 'is-absolute-url',
  32. 'is-plain-obj',
  33. 'longest-streak',
  34. 'micromark',
  35. 'property-information',
  36. 'space-separated-tokens',
  37. 'stringify-entities',
  38. 'trim-lines',
  39. 'trough',
  40. 'web-namespaces',
  41. 'vfile',
  42. 'vfile-location',
  43. 'vfile-message',
  44. 'zwitch',
  45. 'emoticon',
  46. 'direction', // for hast-util-select
  47. 'bcp-47-match', // for hast-util-select
  48. 'parse-entities',
  49. 'character-reference-invalid',
  50. 'is-hexadecimal',
  51. 'is-alphabetical',
  52. 'is-alphanumerical',
  53. 'github-slugger',
  54. 'html-url-attributes',
  55. 'estree-util-is-identifier-name',
  56. 'superjson',
  57. ...listPrefixedPackages([
  58. 'remark-',
  59. 'rehype-',
  60. 'hast-',
  61. 'mdast-',
  62. 'micromark-',
  63. 'unist-',
  64. ]),
  65. ];
  66. // const eazyLogger = require('eazy-logger');
  67. // const logger = eazyLogger.Logger({
  68. // prefix: '[{green:next.config.js}] ',
  69. // useLevelPrefixes: false,
  70. // });
  71. // logger.info('{bold:Listing scoped packages for transpiling:}');
  72. // logger.unprefixed('info', `{grey:${JSON.stringify(packages, null, 2)}}`);
  73. return packages;
  74. };
  75. const optimizePackageImports = [
  76. '@growi/core',
  77. '@growi/editor',
  78. '@growi/pluginkit',
  79. '@growi/presentation',
  80. '@growi/preset-themes',
  81. '@growi/remark-attachment-refs',
  82. '@growi/remark-drawio',
  83. '@growi/remark-growi-directive',
  84. '@growi/remark-lsx',
  85. '@growi/slack',
  86. '@growi/ui',
  87. ];
  88. module.exports = (phase) => {
  89. const { i18n, localePath } = require('./config/next-i18next.config');
  90. /** @type {import('next').NextConfig} */
  91. const nextConfig = {
  92. reactStrictMode: true,
  93. poweredByHeader: false,
  94. pageExtensions: ['page.tsx', 'page.ts', 'page.jsx', 'page.js'],
  95. i18n,
  96. // Bundle server-side dependencies for Pages Router (Next.js 15+)
  97. // Matches App Router behavior: all deps are bundled except those in serverExternalPackages
  98. // Auto-excluded: mongoose, mongodb, express, sharp, and 68 other packages with native bindings
  99. bundlePagesRouterDependencies: true,
  100. serverExternalPackages: [
  101. 'handsontable', // Legacy v6.2.2 requires @babel/polyfill which is unavailable; client-only via dynamic import
  102. ],
  103. // for build
  104. typescript: {
  105. tsconfigPath: 'tsconfig.build.client.json',
  106. },
  107. transpilePackages:
  108. phase !== PHASE_PRODUCTION_SERVER ? getTranspilePackages() : undefined,
  109. experimental: {
  110. optimizePackageImports,
  111. },
  112. /** @param config {import('next').NextConfig} */
  113. webpack(config, options) {
  114. // Auto-wrap getServerSideProps with superjson serialization (replaces next-superjson SWC plugin)
  115. if (options.isServer) {
  116. config.module.rules.push({
  117. test: /\.page\.(tsx|ts)$/,
  118. use: [path.resolve(__dirname, 'src/utils/superjson-ssr-loader.js')],
  119. });
  120. }
  121. if (!options.isServer) {
  122. // Avoid "Module not found: Can't resolve 'fs'"
  123. // See: https://stackoverflow.com/a/68511591
  124. config.resolve.fallback.fs = false;
  125. // exclude packages from the output bundles
  126. config.module.rules.push(
  127. ...[
  128. /dtrace-provider/,
  129. /mongoose/,
  130. /mathjax-full/, // required from marp
  131. /i18next-fs-backend/, // server-only filesystem translation backend (leaks via next-i18next)
  132. /\/bunyan\//, // server-only logging (client uses browser-bunyan via universal-bunyan)
  133. /bunyan-format/, // server-only log formatter (client uses @browser-bunyan/console-formatted-stream)
  134. /[\\/]core-js[\\/]/, // polyfills baked into next-i18next/react-stickynode dist; all APIs natively supported by target browsers (Chrome 64+, Safari 12+)
  135. ].map((packageRegExp) => {
  136. return {
  137. test: packageRegExp,
  138. use: 'null-loader',
  139. };
  140. }),
  141. );
  142. }
  143. // extract sourcemap
  144. if (options.dev) {
  145. config.module.rules.push({
  146. test: /.(c|m)?js$/,
  147. exclude: [/node_modules/, path.resolve(__dirname)],
  148. enforce: 'pre',
  149. use: ['source-map-loader'],
  150. });
  151. }
  152. // setup i18next-hmr
  153. if (!options.isServer && options.dev) {
  154. const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
  155. config.plugins.push(new I18NextHMRPlugin({ localesDir: localePath }));
  156. }
  157. // Log eager vs lazy module counts for dev compilation analysis
  158. if (!options.isServer && options.dev) {
  159. const {
  160. createChunkModuleStatsPlugin,
  161. } = require('./src/utils/next.config.utils');
  162. config.plugins.push(createChunkModuleStatsPlugin());
  163. }
  164. return config;
  165. },
  166. };
  167. // production server — skip bundle analyzer
  168. if (phase === PHASE_PRODUCTION_SERVER) {
  169. return nextConfig;
  170. }
  171. const withBundleAnalyzer = require('@next/bundle-analyzer')({
  172. enabled:
  173. phase === PHASE_PRODUCTION_BUILD &&
  174. (process.env.ANALYZE === 'true' || process.env.ANALYZE === '1'),
  175. });
  176. return withBundleAnalyzer(nextConfig);
  177. };