Przeglądaj źródła

add migrate-to-turbopack spec

Yuki Takei 1 miesiąc temu
rodzic
commit
63ffab7399

+ 404 - 0
.kiro/specs/migrate-to-turbopack/design.md

@@ -0,0 +1,404 @@
+# Design Document: Migrate to Turbopack
+
+## Overview
+
+**Purpose**: This feature migrates GROWI's Next.js bundler from webpack to Turbopack, delivering dramatically faster dev server compilation (5-10x faster Fast Refresh) and improved build performance for the development team.
+
+**Users**: All GROWI developers will benefit from faster HMR, shorter page compilation times, and reduced dev server startup latency.
+
+**Impact**: Changes the build pipeline in `apps/app` by replacing the webpack bundler configuration with Turbopack equivalents while preserving all existing custom functionality (SuperJSON SSR, server-only package exclusion, ESM transpilation).
+
+### Goals
+- Enable Turbopack as the default bundler for `next dev` with the custom Express server
+- Migrate all 6 webpack customizations to Turbopack-compatible equivalents
+- Maintain webpack fallback via environment variable during the transition period
+- Enable Turbopack for production `next build` after dev stability is confirmed
+
+### Non-Goals
+- Migrating from Pages Router to App Router
+- Replacing next-i18next with a different i18n library
+- Rewriting the custom Express server architecture
+- Achieving feature parity for dev module analysis tooling (deferred)
+- Implementing i18n HMR under Turbopack (acceptable tradeoff)
+
+## Architecture
+
+### Existing Architecture Analysis
+
+The current build pipeline uses webpack exclusively, configured via:
+
+1. **Server initialization**: `src/server/crowi/index.ts` calls `next({ dev, webpack: true })` to explicitly opt out of Turbopack
+2. **next.config.ts**: Contains `webpack()` hook with 6 custom configurations (loaders, plugins, resolve fallbacks)
+3. **Build scripts**: `next build --webpack` and `next dev` (via nodemon) both use webpack
+4. **i18n config**: `config/next-i18next.config.js` loads `I18NextHMRPlugin` conditionally in dev mode
+
+Key constraints:
+- Pages Router with `.page.{ts,tsx}` extension convention
+- Custom Express server with middleware stack
+- 70+ ESM packages requiring transpilation
+- Server/client boundary enforcement via null-loader
+
+### Architecture Pattern & Boundary Map
+
+```mermaid
+graph TB
+    subgraph ConfigLayer[Configuration Layer]
+        NextConfig[next.config.ts]
+        I18nConfig[next-i18next.config.js]
+    end
+
+    subgraph ServerInit[Server Initialization]
+        Crowi[crowi/index.ts]
+    end
+
+    subgraph BundlerSelection[Bundler Selection]
+        EnvFlag[USE_WEBPACK env var]
+        EnvFlag -->|true| Webpack[Webpack Pipeline]
+        EnvFlag -->|false/unset| Turbopack[Turbopack Pipeline]
+    end
+
+    subgraph TurboConfig[Turbopack Config]
+        Rules[turbopack.rules]
+        Aliases[turbopack.resolveAlias]
+    end
+
+    subgraph WebpackConfig[Webpack Config - Fallback]
+        WPHook[webpack hook]
+        NullLoader[null-loader rules]
+        SSRLoader[superjson-ssr-loader]
+        Plugins[I18NextHMRPlugin + Stats]
+    end
+
+    Crowi --> EnvFlag
+    NextConfig --> TurboConfig
+    NextConfig --> WebpackConfig
+    Rules --> SuperjsonRule[superjson-ssr-loader server-only]
+    Aliases --> PkgAliases[Package exclusion aliases]
+    Aliases --> FsAlias[fs browser alias]
+    I18nConfig -->|Turbopack mode| NoHMR[No HMR plugins]
+    I18nConfig -->|webpack mode| Plugins
+```
+
+**Architecture Integration**:
+- Selected pattern: Feature-flag phased migration with dual configuration
+- Domain boundaries: Build configuration layer only — no changes to application code, server logic, or page components
+- Existing patterns preserved: Express custom server, Pages Router, SuperJSON SSR, server-client boundary
+- New components: Empty module file, Turbopack config block, env-based bundler selection
+- Steering compliance: Maintains server-client boundary enforcement; aligns with existing build optimization strategy
+
+### Technology Stack
+
+| Layer | Choice / Version | Role in Feature | Notes |
+|-------|------------------|-----------------|-------|
+| Bundler (primary) | Turbopack (Next.js 16 built-in) | Dev and build bundler | Replaces webpack as default |
+| Bundler (fallback) | Webpack (Next.js 16 built-in) | Fallback during transition | Retained via `--webpack` flag |
+| Runtime | Next.js ^16.0.0 | Framework providing both bundler options | `next()` API supports `turbopack` parameter |
+| Loader compat | loader-runner (Turbopack built-in) | Executes webpack loaders in Turbopack | Subset of webpack loader API |
+
+No new external dependencies are introduced. The migration uses only built-in Next.js 16 capabilities.
+
+## System Flows
+
+### Dev Server Startup with Bundler Selection
+
+```mermaid
+sequenceDiagram
+    participant Dev as Developer
+    participant Nodemon as Nodemon
+    participant Crowi as crowi/index.ts
+    participant NextAPI as next() API
+    participant TP as Turbopack
+    participant WP as Webpack
+
+    Dev->>Nodemon: pnpm run dev
+    Nodemon->>Crowi: Execute src/server/app.ts
+    Crowi->>Crowi: Check USE_WEBPACK env var
+
+    alt USE_WEBPACK is set
+        Crowi->>NextAPI: next({ dev, webpack: true })
+        NextAPI->>WP: Initialize webpack pipeline
+        WP->>WP: Apply webpack() hook config
+    else Default - Turbopack
+        Crowi->>NextAPI: next({ dev })
+        NextAPI->>TP: Initialize Turbopack pipeline
+        TP->>TP: Apply turbopack config from next.config.ts
+    end
+
+    NextAPI-->>Crowi: app.prepare() resolves
+    Crowi->>Dev: Server ready
+```
+
+## Requirements Traceability
+
+| Requirement | Summary | Components | Interfaces | Flows |
+|-------------|---------|------------|------------|-------|
+| 1.1 | Remove webpack: true from next() | ServerInit | next() options | Dev startup |
+| 1.2 | Turbopack config in next.config.ts | TurbopackConfig | turbopack key | - |
+| 1.3 | Fast Refresh equivalent | TurbopackConfig | Built-in | Dev startup |
+| 1.4 | Faster HMR for .page.tsx | TurbopackConfig | Built-in | Dev startup |
+| 2.1 | Alias server packages to empty module | ResolveAliasConfig | resolveAlias | - |
+| 2.2 | Resolve fs to false in browser | ResolveAliasConfig | resolveAlias | - |
+| 2.3 | No server packages in client output | ResolveAliasConfig | resolveAlias | - |
+| 3.1 | Register superjson-ssr-loader | TurbopackRulesConfig | turbopack.rules | - |
+| 3.2 | Auto-wrap getServerSideProps | TurbopackRulesConfig | turbopack.rules | - |
+| 3.3 | Identical SuperJSON output | TurbopackRulesConfig | Loader output | - |
+| 3.4 | Fallback mechanism if loader incompatible | TurbopackRulesConfig | - | - |
+| 4.1 | ESM packages without ERR_REQUIRE_ESM | TranspileConfig | transpilePackages | - |
+| 4.2 | Remark/rehype ecosystem bundles correctly | TranspileConfig | transpilePackages | - |
+| 5.1 | Translation changes reflected in browser | I18nConfig | Manual refresh | - |
+| 5.2 | Alternative i18n HMR or documented tradeoff | I18nConfig | Documentation | - |
+| 5.3 | next-i18next functions under Turbopack | I18nConfig | Runtime config | - |
+| 6.1 | Production bundle works | BuildConfig | next build | - |
+| 6.2 | Turbopack or webpack fallback for build | BuildConfig | --webpack flag | - |
+| 6.3 | Production tests pass | BuildConfig | Test suite | - |
+| 7.1 | Alternative module analysis | Deferred | - | - |
+| 7.2 | DUMP_INITIAL_MODULES report | Deferred | - | - |
+| 8.1 | Switch via env var or CLI flag | BundlerSwitch | USE_WEBPACK env | Dev startup |
+| 8.2 | Webpack mode fully functional | WebpackFallback | webpack() hook | - |
+| 8.3 | Dual config in next.config.ts | NextConfigDual | Both configs | - |
+
+## Components and Interfaces
+
+| Component | Domain/Layer | Intent | Req Coverage | Key Dependencies | Contracts |
+|-----------|-------------|--------|--------------|------------------|-----------|
+| ServerInit | Server | Toggle bundler via env var in next() call | 1.1, 1.3, 1.4, 8.1 | next() API (P0) | - |
+| TurbopackConfig | Config | Define turbopack block in next.config.ts | 1.2, 3.1, 3.2 | next.config.ts (P0) | - |
+| ResolveAliasConfig | Config | Alias server-only packages and fs in browser | 2.1, 2.2, 2.3 | empty-module.ts (P0) | - |
+| EmptyModule | Util | Provide empty export file for resolveAlias | 2.1, 2.2 | None | - |
+| I18nConfig | Config | Remove HMR plugins when Turbopack is active | 5.1, 5.2, 5.3 | next-i18next (P1) | - |
+| BuildScripts | Config | Update package.json scripts for Turbopack | 6.1, 6.2, 8.1 | package.json (P0) | - |
+| WebpackFallback | Config | Maintain webpack() hook for fallback | 8.2, 8.3 | next.config.ts (P0) | - |
+
+### Configuration Layer
+
+#### ServerInit — Bundler Toggle
+
+| Field | Detail |
+|-------|--------|
+| Intent | Select Turbopack or webpack based on environment variable when initializing Next.js |
+| Requirements | 1.1, 1.3, 1.4, 8.1 |
+
+**Responsibilities & Constraints**
+- Read `USE_WEBPACK` environment variable to determine bundler choice
+- Pass appropriate option to `next()` API: omit `webpack: true` for Turbopack (default), keep it for webpack fallback
+- Preserve existing ts-node hook save/restore logic around `app.prepare()`
+
+**Dependencies**
+- External: Next.js `next()` API — bundler selection (P0)
+
+**Implementation Notes**
+- Integration: Minimal change in `src/server/crowi/index.ts` — replace hardcoded `webpack: true` with conditional
+- Validation: Verify server starts correctly with both `USE_WEBPACK=1` and without
+- Risks: None — `next()` API parameter is officially documented
+
+#### TurbopackConfig — Rules and Loaders
+
+| Field | Detail |
+|-------|--------|
+| Intent | Configure Turbopack-specific rules for custom loaders in next.config.ts |
+| Requirements | 1.2, 3.1, 3.2, 3.3, 3.4 |
+
+**Responsibilities & Constraints**
+- Register `superjson-ssr-loader` for `*.page.ts` and `*.page.tsx` files with server-only condition
+- Use `turbopack.rules` with `condition: { not: 'browser' }` for server-side targeting
+- Loader must return JavaScript code (already satisfied by existing loader)
+
+**Dependencies**
+- Inbound: next.config.ts — configuration host (P0)
+- External: Turbopack loader-runner — loader execution (P0)
+- External: superjson-ssr-loader.ts — existing loader (P0)
+
+**Contracts**: Service [ ] / API [ ] / Event [ ] / Batch [ ] / State [ ]
+
+**Implementation Notes**
+- Integration: Add `turbopack` key to nextConfig object alongside existing `webpack()` hook
+- Validation: Verify pages with `getServerSideProps` still have SuperJSON wrapping applied
+- Risks: If loader-runner subset causes issues, fallback to code generation approach (pre-process files before build). See `research.md` Risk 2.
+
+##### Turbopack Rules Configuration Shape
+
+```typescript
+// Type definition for the turbopack.rules config
+interface TurbopackRulesConfig {
+  turbopack: {
+    rules: {
+      // Server-only: superjson-ssr-loader for .page.ts/.page.tsx
+      '*.page.ts': Array<{
+        condition: { not: 'browser' };
+        loaders: string[];  // [path.resolve(__dirname, 'src/utils/superjson-ssr-loader.ts')]
+        as: '*.ts';
+      }>;
+      '*.page.tsx': Array<{
+        condition: { not: 'browser' };
+        loaders: string[];
+        as: '*.tsx';
+      }>;
+    };
+    resolveAlias: Record<string, string | { browser: string }>;
+  };
+}
+```
+
+#### ResolveAliasConfig — Package Exclusion
+
+| Field | Detail |
+|-------|--------|
+| Intent | Exclude server-only packages from client bundle using Turbopack resolveAlias |
+| Requirements | 2.1, 2.2, 2.3 |
+
+**Responsibilities & Constraints**
+- Map 7 server-only packages to empty module in browser context
+- Map `fs` to empty module in browser context
+- Use conditional `{ browser: '...' }` syntax for client-only aliasing
+
+**Dependencies**
+- External: EmptyModule file — alias target (P0)
+
+##### Resolve Alias Mapping
+
+```typescript
+// resolveAlias configuration mapping
+interface ResolveAliasConfig {
+  // fs fallback for browser
+  fs: { browser: string };  // path to empty module
+
+  // Server-only packages aliased to empty module in browser
+  'dtrace-provider': { browser: string };
+  mongoose: { browser: string };
+  'mathjax-full': { browser: string };
+  'i18next-fs-backend': { browser: string };
+  bunyan: { browser: string };
+  'bunyan-format': { browser: string };
+  'core-js': { browser: string };
+}
+```
+
+**Implementation Notes**
+- Integration: Add `resolveAlias` under `turbopack` key in next.config.ts
+- Validation: Verify client pages render without "Module not found" errors for each aliased package
+- Risks: The current webpack null-loader uses regex patterns (e.g., `/\/bunyan\//`) which match file paths. Turbopack resolveAlias uses package names. The `bunyan` alias should match imports of `bunyan` but must not interfere with `browser-bunyan`. Test carefully. See `research.md` Risk 3.
+
+#### EmptyModule — Alias Target
+
+| Field | Detail |
+|-------|--------|
+| Intent | Provide a minimal JavaScript module that exports nothing, used as alias target for excluded packages |
+| Requirements | 2.1, 2.2 |
+
+**Responsibilities & Constraints**
+- Export an empty default and named export to satisfy any import style
+- File location: `src/lib/empty-module.ts`
+
+**Implementation Notes**
+- Minimal file: `export default {}; export {};`
+
+#### I18nConfig — HMR Plugin Removal
+
+| Field | Detail |
+|-------|--------|
+| Intent | Conditionally remove i18next-hmr plugins when Turbopack is active |
+| Requirements | 5.1, 5.2, 5.3 |
+
+**Responsibilities & Constraints**
+- Detect whether Turbopack or webpack is active
+- When Turbopack: exclude `I18NextHMRPlugin` from webpack plugins and `HMRPlugin` from i18next `use` array
+- When webpack: preserve existing HMR plugin behavior
+
+**Dependencies**
+- Inbound: next.config.ts — plugin registration (P1)
+- Inbound: next-i18next.config.js — i18next plugin registration (P1)
+- External: i18next-hmr — webpack-only HMR (P1)
+
+**Implementation Notes**
+- Integration: In `next.config.ts`, the `I18NextHMRPlugin` is inside the `webpack()` hook which only runs when webpack is active — no change needed there. In `next-i18next.config.js`, the `HMRPlugin` is loaded regardless of bundler — need to guard it with the same `USE_WEBPACK` env var check or detect Turbopack via absence of webpack context.
+- Validation: Verify next-i18next functions correctly (routing, SSR translations) under Turbopack without HMR plugins
+- Risks: `HMRPlugin` in next-i18next.config.js may reference webpack internals that fail under Turbopack. Guard with env var check. See `research.md` Risk 1.
+
+#### BuildScripts — Package.json Updates
+
+| Field | Detail |
+|-------|--------|
+| Intent | Update build and dev scripts to reflect Turbopack as default with webpack opt-in |
+| Requirements | 6.1, 6.2, 8.1 |
+
+**Responsibilities & Constraints**
+- `build:client` script: keep `next build --webpack` initially (Phase 1), migrate to `next build` in Phase 2
+- `dev` script: no change needed (bundler selection happens in crowi/index.ts, not CLI)
+- `launch-dev:ci` script: inherits bundler from crowi/index.ts
+
+**Implementation Notes**
+- Phase 1: Only server initialization changes. Build scripts remain unchanged.
+- Phase 2: Remove `--webpack` from `build:client` after Turbopack build verification.
+
+## Error Handling
+
+### Error Strategy
+
+Build-time errors from Turbopack migration are the primary concern. All errors surface during compilation and are visible in terminal output.
+
+### Error Categories and Responses
+
+**Module Resolution Errors**: `Cannot resolve 'X'` — indicates a package not properly aliased in `resolveAlias`. Fix: add the missing package to the alias map.
+
+**Loader Execution Errors**: `Turbopack loader failed` — indicates `superjson-ssr-loader` incompatibility. Fix: check loader-runner API usage; fallback to code generation if needed.
+
+**Runtime Errors**: `SuperJSON deserialization failed` — indicates loader transform produced different output. Fix: compare webpack and Turbopack loader output for affected pages.
+
+**i18n Errors**: `Cannot find module 'i18next-hmr'` or HMR plugin crash — indicates HMR plugin loaded in Turbopack mode. Fix: guard HMR plugin loading with env var check.
+
+## Testing Strategy
+
+### Smoke Tests (Manual, Phase 1)
+1. Start dev server with Turbopack (default): verify all pages load without errors
+2. Start dev server with `USE_WEBPACK=1`: verify webpack fallback works identically
+3. Edit a `.page.tsx` file: verify Fast Refresh applies the change
+4. Navigate to a page with `getServerSideProps`: verify SuperJSON data renders correctly
+5. Navigate to a page importing remark/rehype plugins: verify Markdown rendering works
+6. Verify no "Module not found" errors in browser console for server-only packages
+
+### Regression Tests (Automated)
+1. Run existing `vitest run` test suite — all tests must pass (tests don't depend on bundler)
+2. Run `turbo run lint --filter @growi/app` — all lint checks must pass
+3. Run `next build --webpack` — verify webpack build still works (fallback)
+4. Run `next build` (Turbopack) — verify Turbopack production build works (Phase 2)
+
+### Integration Verification
+1. Test i18n: Switch locale in the UI, verify translations load correctly
+2. Test SuperJSON: Visit pages with complex serialized props (ObjectId, dates), verify correct rendering
+3. Test client bundle: Check browser DevTools network tab to confirm excluded packages are not in client JS
+
+## Migration Strategy
+
+### Phase 1: Dev Server Migration (Immediate)
+
+```mermaid
+flowchart LR
+    A[Add turbopack config] --> B[Add empty-module.ts]
+    B --> C[Update crowi/index.ts]
+    C --> D[Guard i18n HMR plugins]
+    D --> E[Smoke test Turbopack dev]
+    E --> F[Smoke test webpack fallback]
+    F --> G[Merge to dev branch]
+```
+
+1. Add `turbopack` configuration block to `next.config.ts` (rules + resolveAlias)
+2. Create `src/lib/empty-module.ts`
+3. Update `src/server/crowi/index.ts`: replace `webpack: true` with `USE_WEBPACK` env var check
+4. Guard `HMRPlugin` in `next-i18next.config.js` with env var check
+5. Run smoke tests with both Turbopack and webpack modes
+6. Merge — all developers now use Turbopack by default for dev
+
+### Phase 2: Production Build Migration (After Verification)
+
+1. Verify Turbopack dev has been stable for a sufficient period
+2. Remove `--webpack` from `build:client` script
+3. Test production build with Turbopack
+4. Run full CI pipeline to validate
+
+### Phase 3: Cleanup (After Full Stability)
+
+1. Remove `webpack()` hook from `next.config.ts`
+2. Remove `USE_WEBPACK` env var check from `crowi/index.ts`
+3. Remove `I18NextHMRPlugin` imports and `HMRPlugin` references entirely
+4. Remove `ChunkModuleStatsPlugin` and related code
+5. Evaluate removing unnecessary `transpilePackages` entries

+ 117 - 0
.kiro/specs/migrate-to-turbopack/requirements.md

@@ -0,0 +1,117 @@
+# Requirements Document
+
+## Introduction
+
+Migrate the GROWI main application (`apps/app`) from webpack to Turbopack for Next.js dev and build pipelines. The primary goal is to dramatically improve dev server compilation speed (HMR / Fast Refresh) while preserving all existing custom functionality.
+
+### Background
+
+GROWI uses a custom Express server with `next({ dev, webpack: true })` to initialize Next.js. As of Next.js 16, the `next()` programmatic API officially supports a `turbopack: true` option (enabled by default), making Turbopack compatible with custom servers. The current webpack opt-out exists solely because custom webpack loaders/plugins in `next.config.ts` require migration to Turbopack equivalents.
+
+### Key Research Findings
+
+1. **Custom server support confirmed**: Next.js 16 `next()` API accepts `turbopack` and `webpack` boolean options. Turbopack is enabled by default — GROWI's `webpack: true` is an explicit opt-out.
+2. **Turbopack loader compatibility**: Turbopack supports a subset of webpack loaders via `loader-runner`. Only loaders returning JavaScript are supported. Conditions (`browser`, `{not: 'browser'}`, `development`, etc.) are available for fine-grained rule targeting.
+3. **resolveAlias**: Replaces webpack `resolve.fallback` and `null-loader` patterns. Supports conditional aliasing (e.g., `{ browser: './empty.js' }`).
+4. **No webpack plugin API**: Turbopack does not support arbitrary webpack plugins. `I18NextHMRPlugin` and `ChunkModuleStatsPlugin` cannot be ported directly.
+
+### Current Webpack Customizations (Migration Scope)
+
+| # | Customization | Type | Turbopack Path |
+|---|---|---|---|
+| 1 | `superjson-ssr-loader` (server-side, `.page.{ts,tsx}`) | Loader | `turbopack.rules` with `{not: 'browser'}` condition |
+| 2 | `resolve.fallback: { fs: false }` (client-side) | Resolve | `turbopack.resolveAlias: { fs: { browser: false } }` |
+| 3 | `null-loader` for 7 packages (client-side) | Loader | `turbopack.resolveAlias` with `{ browser: '' }` or empty module |
+| 4 | `source-map-loader` (dev, non-node_modules) | Loader | Built-in Turbopack source map support |
+| 5 | `I18NextHMRPlugin` (dev, client-side) | Plugin | Drop or replace — Next.js Fast Refresh may cover HMR needs |
+| 6 | `ChunkModuleStatsPlugin` (dev, client-side) | Plugin | Drop or build alternative analysis tooling |
+| 7 | `transpilePackages` (70+ ESM packages) | Config | Supported natively by Turbopack |
+| 8 | `optimizePackageImports` (11 @growi/* packages) | Config | Supported natively by Turbopack |
+
+## Requirements
+
+### Requirement 1: Turbopack Activation for Dev Server
+
+**Objective:** As a developer, I want the Next.js dev server to use Turbopack instead of webpack, so that HMR and page compilation are significantly faster.
+
+#### Acceptance Criteria
+
+1. When the dev server starts, the Next.js build system shall use Turbopack as the bundler (remove `webpack: true` from `next()` call).
+2. The Next.js build system shall accept Turbopack configuration via `turbopack` key in `next.config.ts`.
+3. While the dev server is running with Turbopack, the Next.js build system shall provide Fast Refresh functionality equivalent to the current webpack-based HMR.
+4. When a `.page.tsx` file is modified, the Next.js build system shall apply the change via Fast Refresh within noticeably faster time compared to the current webpack compilation.
+
+### Requirement 2: Server-Only Package Exclusion from Client Bundle
+
+**Objective:** As a developer, I want server-only packages (mongoose, dtrace-provider, bunyan, etc.) excluded from the client bundle, so that the client bundle remains lean and free of Node.js-specific dependencies.
+
+#### Acceptance Criteria
+
+1. The Turbopack configuration shall alias the following packages to empty modules in browser context: `dtrace-provider`, `mongoose`, `mathjax-full`, `i18next-fs-backend`, `bunyan`, `bunyan-format`, `core-js`.
+2. The Turbopack configuration shall resolve `fs` to `false` in browser context to prevent "Module not found: Can't resolve 'fs'" errors.
+3. When a client-side page is rendered, the Next.js build system shall not include any of the excluded server-only packages in the client JavaScript output.
+4. If a new server-only package is accidentally imported from client code, the Next.js build system shall either fail the build or exclude it via the configured aliases.
+
+### Requirement 3: SuperJSON SSR Loader Migration
+
+**Objective:** As a developer, I want the SuperJSON auto-wrapping of `getServerSideProps` to work under Turbopack, so that SSR data serialization continues to function transparently.
+
+#### Acceptance Criteria
+
+1. The Turbopack configuration shall register `superjson-ssr-loader` as a custom loader for `*.page.ts` and `*.page.tsx` files on the server side.
+2. When a `.page.tsx` file exports `getServerSideProps`, the build system shall auto-wrap it with `withSuperJSONProps` during compilation.
+3. The SuperJSON serialization/deserialization shall produce identical output for all existing pages compared to the current webpack-based build.
+4. If the `superjson-ssr-loader` is incompatible with Turbopack's loader-runner subset, the build system shall provide an alternative mechanism (e.g., Babel plugin, SWC plugin, or code generation) that achieves the same transformation.
+
+### Requirement 4: ESM Package Transpilation Compatibility
+
+**Objective:** As a developer, I want all 70+ ESM packages currently listed in `transpilePackages` to work correctly under Turbopack, so that no `ERR_REQUIRE_ESM` errors occur.
+
+#### Acceptance Criteria
+
+1. The Next.js build system shall handle all packages listed in `transpilePackages` without `ERR_REQUIRE_ESM` errors under Turbopack.
+2. When a page importing remark/rehype/micromark ecosystem packages is compiled, the Next.js build system shall bundle them correctly.
+3. If Turbopack natively resolves ESM packages without explicit `transpilePackages` configuration, the build system shall still produce correct output for all affected packages.
+
+### Requirement 5: i18n HMR Behavior
+
+**Objective:** As a developer, I want translation file changes to be reflected in the dev browser without a full page reload, so that i18n development workflow remains productive.
+
+#### Acceptance Criteria
+
+1. While the dev server is running, when a translation JSON file under `public/static/locales/` is modified, the Next.js build system shall reflect the change in the browser.
+2. If the current `I18NextHMRPlugin` is incompatible with Turbopack, the build system shall provide an alternative mechanism for i18n hot reloading or document a manual-reload workflow as an acceptable tradeoff.
+3. The i18n integration (`next-i18next` configuration) shall function correctly under Turbopack without runtime errors.
+
+### Requirement 6: Production Build Compatibility
+
+**Objective:** As a developer, I want the production build (`next build`) to continue working correctly, so that deployment is not disrupted by the Turbopack migration.
+
+#### Acceptance Criteria
+
+1. When `pnpm run build:client` is executed, the Next.js build system shall produce a working production bundle.
+2. The production build shall either use Turbopack (if stable for production) or fall back to webpack (`next build --webpack`) without configuration conflicts.
+3. The production build output shall pass all existing integration and E2E tests.
+4. If Turbopack production builds are not yet stable, the build system shall maintain `--webpack` flag for production while using Turbopack for development only.
+
+### Requirement 7: Dev Module Analysis Tooling
+
+**Objective:** As a developer, I want visibility into module counts and chunk composition during dev builds, so that I can continue optimizing bundle size.
+
+#### Acceptance Criteria
+
+1. If `ChunkModuleStatsPlugin` is incompatible with Turbopack, the build system shall provide an alternative mechanism for analyzing initial vs async module counts during dev compilation.
+2. When `DUMP_INITIAL_MODULES=1` is set, the analysis tooling shall output module breakdown reports comparable to the current `initial-modules-analysis.md` format.
+3. If no Turbopack-compatible analysis tooling is feasible, this requirement may be deferred and documented as a known limitation.
+
+### Requirement 8: Incremental Migration Path
+
+**Objective:** As a developer, I want the ability to switch between Turbopack and webpack during the migration period, so that I can fall back to webpack if Turbopack issues are discovered.
+
+#### Acceptance Criteria
+
+1. The Next.js build system shall support switching between Turbopack and webpack via an environment variable or CLI flag (e.g., `--webpack`).
+2. When webpack mode is selected, all existing webpack customizations shall remain functional without modification.
+3. The `next.config.ts` shall maintain both `webpack()` hook and `turbopack` configuration simultaneously during the migration period.
+4. When the migration is complete and verified, the build system shall remove the webpack fallback configuration in a follow-up cleanup.
+

+ 166 - 0
.kiro/specs/migrate-to-turbopack/research.md

@@ -0,0 +1,166 @@
+# Research & Design Decisions
+
+## Summary
+- **Feature**: `migrate-to-turbopack`
+- **Discovery Scope**: Complex Integration
+- **Key Findings**:
+  - Next.js 16 `next()` programmatic API officially supports `turbopack: true` for custom servers — the primary blocker is resolved
+  - Turbopack `rules` with `condition: { not: 'browser' }` enables server-only loaders, making `superjson-ssr-loader` migration feasible
+  - Turbopack is stable for both dev and production builds in Next.js 16, meaning full migration (not dev-only) is possible
+  - `i18next-hmr` webpack plugin has no Turbopack equivalent; i18n HMR requires alternative approach or manual reload tradeoff
+  - `resolveAlias` with `{ browser: ... }` conditional aliasing replaces both `resolve.fallback` and `null-loader` patterns
+
+## Research Log
+
+### Custom Server + Turbopack Compatibility
+- **Context**: GROWI uses Express custom server calling `next({ dev, webpack: true })`. Need to confirm Turbopack works with programmatic API.
+- **Sources Consulted**:
+  - [Next.js Custom Server Guide](https://nextjs.org/docs/app/guides/custom-server) (v16.1.6, 2026-02-27)
+  - [GitHub Discussion #49325](https://github.com/vercel/next.js/discussions/49325)
+  - [GitHub Issue #65479](https://github.com/vercel/next.js/issues/65479)
+- **Findings**:
+  - The `next()` function in Next.js 16 accepts: `turbopack: boolean` (enabled by default) and `webpack: boolean`
+  - GROWI's current `next({ dev, webpack: true })` explicitly opts out of Turbopack
+  - Switching to `next({ dev })` or `next({ dev, turbopack: true })` enables Turbopack with custom server
+  - No `TURBOPACK=1` env var hack needed — official API parameter available
+- **Implications**: The custom server is NOT a blocker. Migration focus shifts entirely to webpack config equivalents.
+
+### Turbopack Loader System (turbopack.rules)
+- **Context**: `superjson-ssr-loader` must run server-side only on `.page.{ts,tsx}` files.
+- **Sources Consulted**:
+  - [Turbopack Config Docs](https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack) (v16.1.6)
+  - [GitHub Discussion #63150](https://github.com/vercel/next.js/discussions/63150) — server/client conditional loaders
+- **Findings**:
+  - `turbopack.rules` supports glob-based file matching with `condition` for environment targeting
+  - Server-only condition: `condition: { not: 'browser' }`
+  - Browser-only condition: `condition: 'browser'`
+  - Advanced conditions available: `all`, `any`, `not`, `path` (glob/RegExp), `content` (RegExp), `foreign`, `development`, `production`
+  - Loaders must return JavaScript code (our superjson-ssr-loader already does)
+  - Missing loader APIs: `importModule`, `loadModule`, `emitFile`, `this.mode`, `this.target`, `this.resolve`
+  - `superjson-ssr-loader` uses only `source` parameter (simple string transform) — compatible with loader-runner subset
+- **Implications**: superjson-ssr-loader migration is straightforward via `turbopack.rules` with server condition.
+
+### resolveAlias for Package Exclusion (null-loader replacement)
+- **Context**: 7 packages excluded from client bundle via null-loader, plus fs fallback.
+- **Sources Consulted**:
+  - [Turbopack Config Docs](https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack)
+  - [Turbopack Resolve Fallback Forum](https://nextjs-forum.com/post/1189694920328487023)
+  - [GitHub Issue #88540](https://github.com/vercel/next.js/issues/88540) — resolveAlias transitive dependency issues
+- **Findings**:
+  - `turbopack.resolveAlias` supports conditional aliasing: `{ browser: './empty-module.js' }`
+  - For `fs`: `resolveAlias: { fs: { browser: './src/lib/empty.ts' } }` with an empty file
+  - For null-loader replacements: alias each package to an empty module in browser context
+  - Known issue: resolveAlias may not resolve transitive dependencies correctly (GitHub #88540), but our null-loader targets are direct imports not transitive
+  - Regex-based test patterns (e.g., `/\/bunyan\//`) need conversion to package-name aliases
+- **Implications**: Direct 1:1 mapping possible. Need an `empty.ts` module (`export default {}` or empty file). Regex patterns convert to package name strings.
+
+### I18NextHMRPlugin and i18n HMR
+- **Context**: `I18NextHMRPlugin` is a webpack plugin providing HMR for translation JSON files. Turbopack has no plugin API.
+- **Sources Consulted**:
+  - [i18next-hmr npm](https://www.npmjs.com/package/i18next-hmr)
+  - [GitHub Issue #2113](https://github.com/i18next/next-i18next/issues/2113) — Turbopack support
+  - [i18next-hmr GitHub](https://github.com/felixmosh/i18next-hmr)
+- **Findings**:
+  - `i18next-hmr` provides: (1) `I18NextHMRPlugin` webpack plugin for client, (2) `HMRPlugin` i18next plugin for server and client
+  - The webpack plugin watches locale files and triggers HMR updates — no Turbopack equivalent exists
+  - next-i18next + Turbopack compatibility status is unclear/problematic (issue #2113 closed as stale)
+  - `next-i18next` core functionality (i18n routing, SSR) should work independently of bundler, since it uses Next.js i18n config
+  - The HMR plugin is dev-only convenience — not required for functionality
+- **Implications**:
+  - Drop `I18NextHMRPlugin` webpack plugin when using Turbopack
+  - Also drop `HMRPlugin` from `next-i18next.config.js` `use` array when Turbopack is active
+  - Translation changes require manual browser refresh in Turbopack dev mode
+  - Acceptable tradeoff: Turbopack's overall faster compilation outweighs i18n HMR loss
+
+### Turbopack Production Build Status
+- **Context**: Need to determine if production builds can also use Turbopack.
+- **Sources Consulted**:
+  - [Next.js 16 Blog](https://nextjs.org/blog/next-16)
+  - [Turbopack Stable Announcement](https://nextjs.org/blog/turbopack-for-development-stable)
+  - [Progosling: Turbopack Default](https://progosling.com/en/dev-digest/2026-02/nextjs-16-turbopack-default)
+- **Findings**:
+  - Turbopack is stable for both `next dev` and `next build` in Next.js 16
+  - 50%+ dev sessions and 20%+ production builds already on Turbopack (Next.js 15.3+ stats)
+  - `next build` with custom webpack config will fail by default — must use `--webpack` flag or migrate config
+  - Both Turbopack config (`turbopack` key) and webpack config (`webpack()` hook) can coexist in next.config.ts
+- **Implications**: Full migration (dev + build) is feasible. Incremental approach: dev first, then production build.
+
+### ChunkModuleStatsPlugin Replacement
+- **Context**: Custom webpack plugin logging initial/async module counts. Turbopack has no plugin API.
+- **Sources Consulted**: Turbopack API docs, no third-party analysis tools found for Turbopack
+- **Findings**:
+  - Turbopack exposes no compilation hooks or chunk graph API
+  - No equivalent plugin mechanism exists
+  - `@next/bundle-analyzer` may work with Turbopack production builds (uses webpack-bundle-analyzer under the hood — needs verification)
+  - Alternative: Use Turbopack's built-in trace/debug features or browser DevTools for analysis
+- **Implications**: Defer module analysis tooling. Accept this as a temporary limitation. Existing webpack mode can be used for detailed analysis when needed.
+
+### ESM transpilePackages under Turbopack
+- **Context**: 70+ packages in `transpilePackages` for ESM compatibility.
+- **Sources Consulted**: Turbopack docs, Next.js 16 upgrade guide
+- **Findings**:
+  - `transpilePackages` is supported by both webpack and Turbopack in Next.js
+  - Turbopack handles ESM natively with better resolution than webpack
+  - Some packages in the list may not need explicit transpilation under Turbopack
+  - `optimizePackageImports` (experimental) is also supported under Turbopack
+- **Implications**: Keep `transpilePackages` config as-is initially. Test removing entries incrementally after migration verified.
+
+## Architecture Pattern Evaluation
+
+| Option | Description | Strengths | Risks / Limitations | Notes |
+|--------|-------------|-----------|---------------------|-------|
+| Dev-Only Migration | Use Turbopack for dev, keep webpack for build | Low risk, immediate DX gain | Dual config maintenance | Recommended as Phase 1 |
+| Full Migration | Use Turbopack for both dev and build | Single config, simpler maintenance | Higher risk, production impact | Target as Phase 2 |
+| Feature-Flag Approach | Environment variable toggles bundler choice | Maximum flexibility, easy rollback | Complexity in config | Use during transition |
+
+## Design Decisions
+
+### Decision: Dual-Config with Phased Migration
+- **Context**: Need to migrate 6 webpack customizations while maintaining stability
+- **Alternatives Considered**:
+  1. Big-bang migration — convert everything at once, remove webpack config
+  2. Dev-only first — Turbopack for dev, webpack for build
+  3. Feature-flag approach — `USE_WEBPACK=1` toggles between bundlers
+- **Selected Approach**: Option 3 (feature-flag) as implementation vehicle, with Option 2 as the initial target state
+- **Rationale**: Allows any developer to fall back to webpack instantly if Turbopack issues arise. The flag approach naturally supports phased rollout.
+- **Trade-offs**: Slightly more complex next.config.ts during transition. Both configs must be maintained until webpack is fully removed.
+- **Follow-up**: After verification period, remove webpack config and flag in a cleanup task.
+
+### Decision: Drop i18n HMR Plugin
+- **Context**: `I18NextHMRPlugin` is webpack-only. No Turbopack equivalent.
+- **Alternatives Considered**:
+  1. Keep webpack for dev to preserve i18n HMR
+  2. Drop i18n HMR, accept manual refresh for translation changes
+  3. Investigate custom Turbopack-compatible i18n HMR solution
+- **Selected Approach**: Option 2 — drop i18n HMR
+- **Rationale**: The performance gain from Turbopack (5-10x faster Fast Refresh) far outweighs the loss of i18n-specific HMR. Translation editing is a small fraction of dev time.
+- **Trade-offs**: Translation file changes require manual browser refresh. Overall dev experience still dramatically improved.
+- **Follow-up**: Monitor if `i18next-hmr` adds Turbopack support in the future.
+
+### Decision: Empty Module File for resolveAlias
+- **Context**: null-loader replaces modules with empty exports. Turbopack resolveAlias needs an actual file path.
+- **Alternatives Considered**:
+  1. Create `src/lib/empty-module.ts` with `export default {}`
+  2. Use `false` value in resolveAlias (like webpack resolve.fallback)
+  3. Use inline empty string path
+- **Selected Approach**: Option 1 — create a dedicated empty module file
+- **Rationale**: Explicit, documented, easy to understand. Works reliably with conditional browser aliasing.
+- **Trade-offs**: One extra file in the codebase.
+- **Follow-up**: Verify all 7 null-loader targets work correctly with the alias approach.
+
+## Risks & Mitigations
+- **Risk 1**: next-i18next may have runtime issues under Turbopack (not just HMR) — **Mitigation**: Test i18n routing and SSR early; maintain webpack fallback flag
+- **Risk 2**: superjson-ssr-loader may use unsupported loader-runner API features — **Mitigation**: The loader only performs regex-based string transforms on the `source` argument; no advanced APIs used. If issues arise, convert to a Babel plugin or code generation.
+- **Risk 3**: resolveAlias may not handle transitive dependencies from null-loaded packages — **Mitigation**: Current null-loader targets are matched by regex on file paths; resolveAlias uses package names. Test each package individually.
+- **Risk 4**: ESM packages may behave differently under Turbopack resolution — **Mitigation**: Keep `transpilePackages` list unchanged initially; test pages using remark/rehype ecosystem.
+- **Risk 5**: Production build regression — **Mitigation**: Phase 1 keeps webpack for production; Phase 2 migrates production only after dev is verified stable.
+
+## References
+- [Next.js Custom Server Guide](https://nextjs.org/docs/app/guides/custom-server) — confirms `turbopack` option in `next()` API
+- [Next.js Turbopack Config](https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack) — rules, resolveAlias, conditions
+- [Next.js 16 Release Blog](https://nextjs.org/blog/next-16) — Turbopack stable for dev and build
+- [Next.js Upgrade Guide v16](https://nextjs.org/docs/app/guides/upgrading/version-16) — migration steps
+- [GitHub Discussion #63150](https://github.com/vercel/next.js/discussions/63150) — server/client conditional loaders
+- [GitHub Discussion #49325](https://github.com/vercel/next.js/discussions/49325) — custom server + Turbopack
+- [i18next-hmr](https://github.com/felixmosh/i18next-hmr) — webpack/vite only, no Turbopack support
+- [Turbopack Loader API Limitations](https://nextjs.org/docs/app/api-reference/turbopack) — missing features documented

+ 22 - 0
.kiro/specs/migrate-to-turbopack/spec.json

@@ -0,0 +1,22 @@
+{
+  "feature_name": "migrate-to-turbopack",
+  "created_at": "2026-03-04T00:00:00Z",
+  "updated_at": "2026-03-04T14:00:00Z",
+  "language": "en",
+  "phase": "tasks-generated",
+  "approvals": {
+    "requirements": {
+      "generated": true,
+      "approved": true
+    },
+    "design": {
+      "generated": true,
+      "approved": true
+    },
+    "tasks": {
+      "generated": true,
+      "approved": false
+    }
+  },
+  "ready_for_implementation": false
+}

+ 91 - 0
.kiro/specs/migrate-to-turbopack/tasks.md

@@ -0,0 +1,91 @@
+# Implementation Plan
+
+## Phase 1: Dev Server Turbopack Migration
+
+- [ ] 1. Create empty module and Turbopack configuration foundation
+- [ ] 1.1 (P) Create the empty module file used as the alias target for excluded server-only packages
+  - Create a minimal TypeScript module at the designated location that exports an empty default and named export
+  - The module satisfies any import style (default, named, namespace) so that aliased packages resolve without errors
+  - _Requirements: 2.1, 2.2_
+
+- [ ] 1.2 (P) Add the Turbopack configuration block to the Next.js config with resolve aliases for server-only package exclusion
+  - Add a `turbopack` key to the Next.js config object containing `resolveAlias` entries
+  - Alias `fs` to the empty module in browser context to prevent "Module not found" errors on the client
+  - Alias all 7 server-only packages (`dtrace-provider`, `mongoose`, `mathjax-full`, `i18next-fs-backend`, `bunyan`, `bunyan-format`, `core-js`) to the empty module in browser context
+  - Use the conditional `{ browser: '...' }` syntax for each alias so that server-side resolution remains unaffected
+  - Verify that the `bunyan` alias does not interfere with `browser-bunyan` (different package name, no collision expected)
+  - Keep the existing `webpack()` hook untouched — both configs coexist
+  - _Requirements: 1.2, 2.1, 2.2, 2.3, 8.3_
+
+- [ ] 1.3 Add the superjson-ssr-loader as a Turbopack custom loader rule for server-side page files
+  - Register the existing `superjson-ssr-loader` under `turbopack.rules` for `*.page.ts` and `*.page.tsx` file patterns
+  - Apply the `condition: { not: 'browser' }` condition so the loader runs only on the server side
+  - Set the `as` output type to `*.ts` / `*.tsx` respectively so Turbopack continues processing the transformed output
+  - The loader performs a simple regex-based source transform and returns JavaScript — no unsupported loader-runner APIs are used
+  - _Requirements: 1.2, 3.1, 3.2, 3.3, 3.4_
+
+- [ ] 2. Update server initialization to toggle between Turbopack and webpack
+- [ ] 2.1 Replace the hardcoded `webpack: true` in the custom server with environment-variable-based bundler selection
+  - Read the `USE_WEBPACK` environment variable at server startup
+  - When `USE_WEBPACK` is set (truthy), pass `{ dev, webpack: true }` to preserve the existing webpack pipeline
+  - When `USE_WEBPACK` is not set (default), omit the `webpack` option so Turbopack activates as the Next.js 16 default
+  - Preserve the existing ts-node hook save/restore logic surrounding `app.prepare()`
+  - _Requirements: 1.1, 1.3, 1.4, 8.1_
+
+- [ ] 3. Guard i18n HMR plugin loading for Turbopack compatibility
+- [ ] 3.1 Conditionally skip the i18next-hmr plugin in the i18n configuration when Turbopack is active
+  - In the next-i18next configuration file, check the `USE_WEBPACK` environment variable before loading the `HMRPlugin`
+  - When `USE_WEBPACK` is not set (Turbopack mode), exclude `HMRPlugin` from the `use` array to prevent webpack-internal references from crashing
+  - When `USE_WEBPACK` is set (webpack mode), preserve the existing `HMRPlugin` behavior for both server and client
+  - The `I18NextHMRPlugin` in the webpack hook of next.config.ts requires no change — it only executes when webpack is active
+  - Translation file changes under Turbopack require a manual browser refresh (documented tradeoff)
+  - _Requirements: 5.1, 5.2, 5.3_
+
+- [ ] 4. Smoke test the Turbopack dev server and webpack fallback
+- [ ] 4.1 Verify the dev server starts and pages load correctly under Turbopack
+  - Start the dev server without `USE_WEBPACK` and confirm it initializes with Turbopack
+  - Navigate to representative pages and verify they render without errors
+  - Confirm no "Module not found" or "Cannot resolve" errors appear in the terminal or browser console
+  - Visit a page that uses `getServerSideProps` and verify SuperJSON-serialized data renders correctly
+  - Visit a page that imports remark/rehype/micromark ecosystem packages and verify Markdown rendering works
+  - Switch the browser locale and verify i18n translations load correctly
+  - Edit a `.page.tsx` file and verify Fast Refresh applies the change
+  - _Requirements: 1.1, 1.3, 1.4, 2.3, 3.2, 3.3, 4.1, 4.2, 5.3_
+
+- [ ] 4.2 Verify the webpack fallback mode works identically to the pre-migration state
+  - Start the dev server with `USE_WEBPACK=1` and confirm webpack initializes
+  - Repeat the same page navigation checks to ensure no regression
+  - Confirm the `I18NextHMRPlugin` and `HMRPlugin` are active in webpack mode
+  - Confirm the `ChunkModuleStatsPlugin` logs module stats in webpack mode
+  - _Requirements: 8.1, 8.2, 8.3_
+
+- [ ] 4.3 Run existing automated tests and lint checks
+  - Run the vitest test suite to confirm no test regressions
+  - Run lint checks (typecheck, biome, stylelint) to confirm no new errors
+  - Run the production build with `--webpack` to confirm it still works
+  - _Requirements: 6.1, 6.2, 6.3_
+
+## Phase 2: Production Build Migration (Deferred)
+
+- [ ] 5. Migrate production build from webpack to Turbopack
+- [ ] 5.1 Remove the `--webpack` flag from the production client build script
+  - Update the `build:client` script to use `next build` without the `--webpack` flag
+  - Run the full production build and verify it completes without errors
+  - Run the vitest test suite against the Turbopack-built output
+  - _Requirements: 6.1, 6.2, 6.3_
+
+## Phase 3: Cleanup (Deferred)
+
+- [ ] 6. Remove webpack fallback configuration and deprecated plugins
+- [ ] 6.1 Remove the `webpack()` hook, `USE_WEBPACK` env var check, and deprecated plugin code
+  - Remove the entire `webpack()` hook function from the Next.js config
+  - Remove the `USE_WEBPACK` conditional in the custom server and use the Turbopack default unconditionally
+  - Remove `I18NextHMRPlugin` import and usage from the Next.js config
+  - Remove `HMRPlugin` import and conditional loading from the next-i18next config
+  - Remove `ChunkModuleStatsPlugin` and its helper code from the config utilities module
+  - Evaluate whether any `transpilePackages` entries can be removed under Turbopack
+  - _Requirements: 8.4_
+
+## Deferred Requirements
+
+- **Requirement 7 (Dev Module Analysis Tooling)**: Requirements 7.1, 7.2 are intentionally deferred. Turbopack has no plugin API for compilation hooks, and no equivalent analysis tooling exists. When detailed module analysis is needed, developers can temporarily use `USE_WEBPACK=1` during the transition period. A Turbopack-native solution may emerge as the ecosystem matures.