apps/app/.claude/skills/build-optimization/SKILL.md
GROWI apps/app Turbopack configuration, module optimization, and build measurement tooling. Auto-invoked when working in apps/app.
npx skillsauth add growilabs/growi build-optimizationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
^16.0.0) with Turbopack bundler (default)next build; Dev: Express server calls next({ dev }) which uses Turbopack by default^18.2.0 — Pages Router has full React 18 support in v16webpack() hook, no --webpack flag)turbopack.rules)| Rule | Pattern | Condition | Purpose |
|------|---------|-----------|---------|
| superjson-ssr-loader | *.page.ts, *.page.tsx | { not: 'browser' } (server-only) | Auto-wraps getServerSideProps with SuperJSON serialization |
next.config.ts under turbopack.rulescondition: { not: 'browser' } restricts the loader to server-side compilation onlyas: '*.ts' / as: '*.tsx' tells Turbopack to continue processing the transformed output as TypeScriptturbopack.resolveAlias)4 server-only packages + fs are aliased to ./src/lib/empty-module.ts in browser context:
| Package | Reason |
|---------|--------|
| fs | Node.js built-in, not available in browser |
| mongoose | MongoDB driver, server-only |
| i18next-fs-backend | File-system i18n loader, server-only |
| core-js | Server-side polyfills |
{ browser: './src/lib/empty-module.ts' } syntax so server-side resolution is unaffectedresolveAlias requires relative paths (e.g., ./src/lib/empty-module.ts), not absolute paths — absolute paths cause "server relative imports are not implemented yet" errorsresolveAlias with the same patternThe next-superjson SWC plugin was replaced by a custom loader:
superjson-ssr-loader.ts auto-wraps getServerSideProps in .page.{ts,tsx} files with withSuperJSONProps() via Turbopack ruleswithSuperJSONProps() in src/pages/utils/superjson-ssr.ts serializes props via superjson_app.page.tsx calls deserializeSuperJSONProps() for centralized deserialization_app.page.tsx (ObjectId, PageRevisionWithMeta):global SyntaxTurbopack only supports the function form :global(...). The block form :global { ... } is NOT supported:
// WRONG — Turbopack rejects this
.parent :global {
.child { color: red; }
}
// CORRECT — function form
.parent {
:global(.child) { color: red; }
}
Nested blocks must also use function form:
// WRONG
.parent :global {
.child {
.grandchild { }
}
}
// CORRECT
.parent {
:global(.child) {
:global(.grandchild) { }
}
}
:local / &:local: Not supported. Inside :global(...), properties are locally scoped by default — remove &:local wrappers@extend with :global(): @extend .class fails when target is wrapped in :global(.class) — Sass doesn't match them as the same selector. Use shared selector groups (comma-separated selectors) instead*zoom:1, *display:inline, filter:alpha() cannot be parsed by Turbopack's CSS parser (lightningcss). Avoid CSS files containing these hacksGlobal CSS cannot be imported from files other than _app.page.tsx under Turbopack Pages Router. See the vendor-styles-components skill for the precompilation system that handles per-component vendor CSS.
serverExternalPackages: ['handsontable'] — packages excluded from server-side bundlingoptimizePackageImports — 11 @growi/* packages configured (expansion to third-party packages was tested and reverted — it increased dev module count)Techniques that have proven effective for reducing module count, ordered by typical impact:
| Technique | When to Use |
|-----------|-------------|
| next/dynamic({ ssr: true }) | Heavy rendering pipelines (markdown, code highlighting) that can be deferred to async chunks while preserving SSR |
| next/dynamic({ ssr: false }) | Client-only heavy components (e.g., Mermaid diagrams, interactive editors) |
| Subpath imports | Packages with large barrel exports (e.g., date-fns/format instead of date-fns) |
| Deep ESM imports | Packages that re-export multiple engines via barrel (e.g., react-syntax-highlighter/dist/esm/prism-async-light) |
| resolveAlias | Server-only packages leaking into client bundle via transitive imports |
| Lightweight replacements | Replace large libraries used for a single feature (e.g., tinykeys instead of react-hotkeys, regex instead of validator) |
optimizePackageImports to third-party packages — In dev mode, this resolves individual sub-module files instead of barrel, resulting in MORE module entries. Reverted.states/, features/) are small and well-scoped; refactoring had no measurable impact.I18NextHMRPlugin was removed during the Turbopack migration. Translation file changes require a manual browser refresh. The performance gain from Turbopack (faster Fast Refresh overall) outweighs the loss of i18n-specific HMR. Monitor if i18next-hmr adds Turbopack support in the future.
development
Vendor CSS precompilation system for Turbopack compatibility. How to add third-party CSS to components without violating Pages Router global CSS restriction. Auto-invoked when working in apps/app.
testing
Auto-invoked when modifying origin-based conflict detection, revision validation logic, or isUpdatable() method. Explains the two-stage origin check mechanism for conflict detection and its separation from diff detection.
testing
Fix broken symlinks in .next/node_modules/ — diagnose, decide allowlist vs dependencies, and verify
testing
GROWI main application (apps/app) specific patterns for Next.js, Jotai, SWR, and testing. Auto-invoked when working in apps/app.