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.
Turbopack (Pages Router) strictly enforces: global CSS can only be imported from _app.page.tsx. Components cannot import 'package/style.css' directly — Turbopack rejects these at compile time.
Centralizing all vendor CSS in _app would degrade FCP for pages that don't need those styles.
src/styles/vendor.scsssimplebar-react)vite.vendor-styles-commons.ts into src/styles/prebuilt/_app.page.tsx*.vendor-styles.ts entry points into *.vendor-styles.prebuilt.ts using ?inline CSS import suffixComponentName.vendor-styles.ts): imports CSS via Vite ?inline suffix, which inlines CSS as a string<style> tag and appends CSS to document.headpre:styles-components Turborepo task): compiles entry points into *.vendor-styles.prebuilt.ts.prebuilt.ts file instead of raw CSS// @ts-nocheck -- Processed by Vite only; ?inline is a Vite-specific import suffix
import css from 'some-package/dist/style.css?inline';
const s = document.createElement('style');
s.textContent = css;
document.head.appendChild(s);
For multiple CSS sources in one component:
// @ts-nocheck
import css1 from 'package-a/style.css?inline';
import css2 from 'package-b/style.css?inline';
const s = document.createElement('style');
s.textContent = css1 + css2;
document.head.appendChild(s);
| Entry Point | CSS Sources | Consuming Components |
|---|---|---|
Renderer.vendor-styles.ts | @growi/remark-lsx, @growi/remark-attachment-refs, katex | renderer.tsx |
GrowiEditor.vendor-styles.ts | @growi/editor | PageEditor, CommentEditor |
HandsontableModal.vendor-styles.ts | handsontable (non-full variant) | HandsontableModal |
DateRangePicker.vendor-styles.ts | react-datepicker | DateRangePicker |
RevisionDiff.vendor-styles.ts | diff2html | RevisionDiff |
DrawioViewerWithEditButton.vendor-styles.ts | @growi/remark-drawio | DrawioViewerWithEditButton |
ImageCropModal.vendor-styles.ts | react-image-crop | ImageCropModal |
Presentation.vendor-styles.ts | @growi/presentation | Presentation, Slides |
{ComponentName}.vendor-styles.ts next to the consuming component:
// @ts-nocheck
import css from 'new-package/dist/style.css?inline';
const s = document.createElement('style');
s.textContent = css;
document.head.appendChild(s);
import 'new-package/dist/style.css' with:
import './ComponentName.vendor-styles.prebuilt';
pnpm run pre:styles-components (or let Turborepo handle it during dev/build).prebuilt.js file is git-ignored and auto-generatedDecision guide: If the CSS is needed on nearly every page, add it to the commons track (vendor.scss) instead.
When vendor CSS references external assets (e.g., KaTeX @font-face with url(fonts/KaTeX_*.woff2)):
src/assets/ during buildmoveAssetsToPublic plugin (in vite.vendor-styles-components.ts) relocates them to public/static/fonts//assets/ to /static/fonts/express.static(crowi.publicDir) middlewarepublic/static/fonts/ and src/**/*.vendor-styles.prebuilt.ts are git-ignoredturbo.json tasks:
pre:styles-components → build (dependency)
dev:pre:styles-components → dev (dependency)
Inputs: vite.vendor-styles-components.ts, src/**/*.vendor-styles.ts, package.json
Outputs: src/**/*.vendor-styles.prebuilt.ts, public/static/fonts/**
<style> tags at runtime — not available during SSR. Most consuming components use next/dynamic({ ssr: false }), so FOUC is not a practical concern@ts-nocheck: Required because ?inline is a Vite-specific import suffix not understood by TypeScripthandsontable/dist/handsontable.css (non-full, non-minified). The "full" variant (handsontable.full.min.css) contains IE CSS hacks (*zoom:1, filter:alpha()) that Turbopack's CSS parser (lightningcss) cannot parse. The "full" variant also includes Pikaday which is unused.