@animus-ui/next-plugin
v0.1.25
Published
Animus static CSS extraction for Next.js (webpack)
Maintainers
Readme
@animus-ui/next-plugin
Static CSS extraction for Next.js (15 and 16). Wraps next.config to integrate the Animus extraction pipeline — webpack and Turbopack.
Install
npm install @animus-ui/next-plugin @animus-ui/systemSetup
// next.config.mjs
import { withAnimus } from '@animus-ui/next-plugin';
const nextConfig = {
// your existing Next.js config
};
export default withAnimus({
system: './src/ds.ts',
})(nextConfig);Turbopack
Turbopack support activates automatically whenever the process runs under
Turbopack (next dev --turbopack, or Next 16 where Turbopack is the
default) — no config change needed. withAnimus(...)(nextConfig) then
returns a config function: Next calls it with its phase, and extraction runs
before it resolves. Only next dev analyzes in development mode and starts
a watcher that re-analyzes on source changes; next build prunes. Per-file
transforms run in a stateless loader fed by generated .animus/ artifacts.
tsconfig paths aliases are honored.
Apply config wrappers that expect an object, such as withMDX, to
nextConfig before passing it to withAnimus:
withAnimus(options)(withMDX(nextConfig)). Applied outside, a wrapper that
copies the config into a new object keeps its own keys and drops Animus's.
If your config is a function of its own, pass Next's phase through:
export default (phase: string) =>
withAnimus({ system: './src/ds.ts', phase })(nextConfig);The result can also be awaited, as in
{ ...(await withAnimus(options)(nextConfig)) }. Without a phase it then
reads NODE_ENV=development as next dev and anything else as a build. A
next.config.ts that assigns module.exports = withAnimus(...)(...) is
awaited the same way when Next compiles it, so write export default, or
pass the phase from a config function.
Because next.config resolves only after that first extraction, a source the
parser cannot finish at startup rejects the config before any watcher exists:
fix the syntax error and start Next again. Once the watcher runs, a later
syntax error keeps the last published artifacts and holds further edits until
the file parses, then publishes them together.
Control it explicitly with the turbopack option:
export default withAnimus({
system: './src/ds.ts',
turbopack: { mode: 'auto' }, // 'auto' (default) | 'on' | 'off'
})(nextConfig);Under webpack (next dev / next build on 15, or --webpack on 16) the
plugin behaves exactly as before.
Appearance bootstrap (no-FOUC color mode)
Optional. Restores a persisted color mode before first paint with zero runtime — a generated, dependency-free inline snippet whose CSP hash is computed at build time from the exact bytes generated.
The plugin deliberately injects nothing: CSP nonces need request-time control
the bundler does not have, so the application places the artifact. Generate
it in a server-only module and inline it as the first child of <head>.
App Router:
// appearance-bootstrap.ts — server-only; never import from a client component
import { createAppearanceBootstrap } from '@animus-ui/system/bootstrap';
import { theme } from './src/ds';
export const appearanceBootstrap = createAppearanceBootstrap(theme);// app/layout.tsx
import { appearanceBootstrap } from '../appearance-bootstrap';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<script
data-animus-bootstrap=""
dangerouslySetInnerHTML={{ __html: appearanceBootstrap.code }}
/>
</head>
<body>{children}</body>
</html>
);
}Pages Router: same artifact, placed as the first child of <Head> in
pages/_document.tsx, with suppressHydrationWarning on <Html>.
Two rules either way:
suppressHydrationWarningon the root element is required, not cosmetic — the snippet legitimately mutatesdata-color-modebetween SSR and hydration.- Serve a CSP that authorizes the script from the artifact itself: either
script-src '<artifact.cspHash>'(single-quoted, derived at build time — never hand-copied; a renamed mode changes the hash) or a per-request nonce you add to the script tag. The full CSP footguns are documented in the@animus-ui/vite-pluginREADME.
What It Does
- Transforms
@animus-ui/systembuilder chains into static CSS (webpack loader or Turbopack loader rule) - Emits extracted CSS as a separate asset with
@layerordering - Post-processes the emitted sheet with Lightning CSS — autoprefixed for your
targets(browserslist query; defaults to the project config), minified in production (minifyoverrides) - Works with both App Router and Pages Router
- Supports RSC — no runtime style injection means server components work out of the box
License
MIT
