@unterberg/vite-beasties-output
vite-beasties-output
Post-build Vite plugin that runs Beasties against already generated HTML output.
Small post-build adapter around Beasties. It focuses on processing emitted HTML files on disk rather than participating in Vite’s in-pipeline HTML transformation.
That makes it suitable for SSG, prerendered, or otherwise statically emitted HTML output. It does not modify runtime SSR responses directly. For SSR-only applications, this plugin only has an effect if the build produces actual .html files that can be processed after the build.
What it does
- Runs only during
build - Runs late with
enforce: 'post' - Scans generated
.htmlfiles from the configured output directory, or processes explicitincludepaths/globs - Processes each HTML file with Beasties
- Writes optimized HTML back to disk
- Logs how many HTML files were processed, unless Beasties logging is
silent
Beasties may add data-beasties-container to the top-level <html> tag while processing. The plugin removes that marker from final output to avoid leaking optimization-only attributes into hydrated documents. Custom Beasties containers inside the document are preserved.
Installation
pnpm add -D @unterberg/vite-beasties-outputUsage
import { defineConfig } from 'vite'
import { viteBeastiesOutput } from '@unterberg/vite-beasties-output'
export default defineConfig({
plugins: [
viteBeastiesOutput({
outputDirectory: 'dist/client',
}),
],
})For Vike prerender builds, dist/client is usually the relevant output directory because prerendered HTML files are written there.
You can also process specific generated HTML files instead of scanning the whole output directory:
viteBeastiesOutput({
include: ['dist/client/index.html', 'dist/client/docs/**/*.html'],
})For SSR-only Vike builds without prerendering, the server output usually contains runtime modules such as page_*.mjs files instead of final .html files. This plugin does not modify those runtime SSR modules and therefore cannot inline critical CSS into SSR responses by itself.
Vike full builds can invoke Vite more than once. In that case, you may see an early Processed 0 HTML files message during the client build before Vike has prerendered any HTML, followed by a later message after the prerendered HTML files exist.
If outputDirectory is omitted, the plugin uses Vite's build.outDir.
Output directory
Point outputDirectory at the directory that contains the generated HTML files you want to process. The plugin scans that directory recursively for .html files and lets Beasties resolve linked stylesheets from that output root.
dist/client/
├─ index.html
├─ nested/
│ └─ page.html
└─ assets/
└─ app.css
Options
outputDirectory
The output directory to scan. Relative paths are resolved from Vite's project root. Absolute paths are used as-is.
viteBeastiesOutput({
outputDirectory: 'dist/client',
})When omitted, this defaults to Vite's build.outDir.
In Vike prerender projects, this is often not enough because the build may involve separate client and server output directories. In that case, point outputDirectory directly at the directory containing the generated .html files, usually dist/client.
include
Specific HTML files or glob patterns to process instead of scanning the output directory. Relative paths are resolved from Vite's project root. Supported glob tokens are *, **, and ?.
viteBeastiesOutput({
include: 'dist/client/index.html',
})viteBeastiesOutput({
include: ['dist/client/index.html', 'dist/client/docs/**/*.html'],
})When include is set, the plugin only processes matched .html files. If outputDirectory is also set, Beasties resolves linked CSS from that directory. If outputDirectory is omitted, the plugin infers the Beasties root from the include path or glob base.
explicitContainersOnly
When true, the plugin only processes HTML files that already contain data-beasties-container. Files without an explicit container are left unchanged, which prevents Beasties from falling back to its default whole-document critical CSS pass.
viteBeastiesOutput({
explicitContainersOnly: true,
})This is useful when you want critical CSS extraction to be opt-in per page or per region. The default is false, matching Beasties' normal behavior.
beastiesOptions
Pass supported Beasties options. The plugin controls path and publicPath internally based on outputDirectory and Vite's resolved base.
viteBeastiesOutput({
beastiesOptions: {
preload: 'swap', // 'body' | 'media' | 'swap' | 'swap-high' | 'swap-low' | 'js' | 'js-lazy' | false
compress: true, // Compress critical CSS
logLevel: 'warn', // 'info' | 'warn' | 'error' | 'trace' | 'debug' | 'silent'
pruneSource: false, // Do not remove source CSS files
reduceInlineStyles: false, // Preserve inline <style> tags rendered by the app/framework
allowRules: [], // Merged after the plugin's narrow Tailwind spacing defaults
inlineFonts: false, // Do not inline @font-face rules
keyframes: 'critical', // 'critical' | 'all' | 'none'
},
})Defaults
The plugin ships with sensible defaults:
{
preload: 'swap',
pruneSource: false,
reduceInlineStyles: false,
allowRules: [
/^:where\(\.(?:[^ >+~)]*\\:)*-?space-[xy]-/,
],
compress: true,
logLevel: 'warn',
}DaisyUI and theme variables
The plugin does not automatically parse or re-inject DaisyUI/Tailwind theme rules. Beasties runs after Tailwind and DaisyUI have generated the final CSS, so comments placed around @plugin 'daisyui/theme', @theme, or @custom-variant directives may be removed before Beasties can see them.
For DaisyUI theme variables, prefer Beasties' native allowRules option:
viteBeastiesOutput({
outputDirectory: 'dist/client',
beastiesOptions: {
allowRules: [
/data-theme=.*dark/,
/data-theme=.*light/,
/^:root:has\(input\.theme-controller/,
/^:where\(:root\)$/,
],
},
})Beasties include comments are still useful for plain CSS rules, but only when those comments survive into the built CSS file that Beasties processes:
/* beasties:include */
.always-critical {
color: currentColor;
}How it differs from vite-plugin-beasties
@unterberg/vite-beasties-output is not intended to replace the official vite-plugin-beasties.
Use vite-plugin-beasties for regular Vite projects where HTML is processed through Vite’s transformIndexHtml hook. Use @unterberg/vite-beasties-output when your final HTML files already exist in an output directory after the build, for example in SSG or prerender setups.
This plugin does not inject critical CSS into runtime SSR responses. It only processes .html files that already exist on disk.
| Feature | vite-plugin-beasties |
@unterberg/vite-beasties-output |
|---|---|---|
| Main purpose | Generic Vite integration for Beasties | Post-build processing for already emitted HTML files |
| Best suited for | Standard Vite apps using Vite’s HTML pipeline | SSG, prerendered, or statically emitted HTML output |
| Build hook | transformIndexHtml |
closeBundle |
| When it runs | During Vite’s HTML transformation | After the build output has been written |
| HTML source | HTML passed through Vite’s HTML transform pipeline | .html files found in the configured output directory |
| Output handling | Returns transformed HTML to Vite | Writes optimized HTML files back to disk |
| Output directory control | Uses Vite’s build.outDir internally |
Explicit outputDirectory option |
| Runtime SSR support | Can work only if SSR HTML passes through Vite’s HTML transform flow | Not supported directly; runtime SSR responses are not modified |
| Vike prerender compatibility | May not process final prerendered HTML if it is emitted outside the normal Vite HTML transform flow | Designed for this case |
| Vike SSR-only compatibility | Not the primary target | Not supported unless .html files are emitted to disk |
Beasties path / publicPath |
Controlled internally from Vite config | Controlled internally from outputDirectory and Vite base |
| CSS pruning default | Enabled by default in the plugin implementation | Disabled by default |
| Intended relationship | Official generic Vite plugin | Narrow output-directory adapter |
| Recommended use | Use this first for regular Vite HTML builds | Use only when final HTML exists after build and needs post-processing |
Runtime SSR limitation
This plugin is intentionally file-based. It scans an output directory for .html files and processes those files with Beasties after the build has finished.
It does not hook into a framework’s runtime rendering pipeline. If an application renders HTML dynamically at request time, there is no final HTML file for this plugin to process.
Supporting runtime SSR critical CSS would require integrating Beasties, or precomputed critical CSS, directly into the server rendering flow. That is outside the scope of this plugin.
How it works
The plugin runs after your Vite build completes. It:
- Finds HTML files from
include, or scans the configured output directory recursively for.htmlfiles - For each HTML file, processes it through Beasties to extract and inline critical CSS, unless
explicitContainersOnlyis enabled and the file has nodata-beasties-container - Writes the optimized HTML back to disk
- Logs the number of HTML files processed when
beastiesOptions.logLevelis notsilent
Limitations
- Static HTML only: The plugin only processes
.htmlfiles that exist on disk after the build. - No direct runtime SSR injection: SSR responses rendered at request time are not modified.
- Output root must match public paths: Absolute stylesheet URLs are resolved from
outputDirectory, or from the inferredincludebase whenoutputDirectoryis omitted, using Vite's configuredbase. - Beasties path control: Beasties
pathandpublicPathare controlled by the plugin based onoutputDirectoryand Vite's resolved config. - Explicit container mode: Set
explicitContainersOnly: truewhen unmarked HTML should be skipped instead of letting Beasties evaluate the whole document. - Inline style preservation: Existing inline
<style>tags are preserved by default to avoid mutating framework-rendered HTML before hydration. SetbeastiesOptions.reduceInlineStyles: trueif you explicitly want Beasties to process and merge inline styles. - Tailwind child spacing: Tailwind emits
space-x-*andspace-y-*utilities as:where(...)child selectors that Beasties' fast selector matcher can miss. The plugin includes a narrow defaultallowRulespattern for those utilities and merges userallowRulesafter it. - Multiple Beasties containers: Beasties evaluates only the first
data-beasties-containerit finds. When multiple containers are present, the plugin temporarily promotes<body>as the processing container so all marked critical regions can contribute CSS, then removes that plugin-added body marker from final HTML. - Beasties owns CSS selection: The plugin does not add extra CSS parsing or framework-specific rule preservation. Use Beasties options such as
allowRulesor CSS comments like/* beasties:include */when a project needs explicit rule inclusion.
Troubleshooting
Expected rules are missing from critical CSS
Use Beasties' native include mechanisms for rules that cannot be discovered from the generated HTML. For DaisyUI or theme variables, see DaisyUI and theme variables.
If you use multiple data-beasties-container regions, make sure you are on a plugin version that includes multiple-container normalization. Beasties itself only evaluates the first container.
Plugin not running
Verify:
- You're running
pnpm buildor another production build command - Your Vite config has the plugin in the
pluginsarray outputDirectorypoints at the generated HTML output root- Your build actually emits
.htmlfiles
For Vike prerender builds, an early Processed 0 HTML files log can be normal because Vike writes prerendered HTML after the first Vite build phase. Check the final log message and the generated dist/client/**/*.html files.
Processed 0 HTML files
This usually means the configured output directory does not contain generated .html files.
For prerendered or SSG builds, check where the framework writes its final HTML output and point outputDirectory there.
For runtime SSR-only builds, this is expected if the build only emits server modules such as .mjs files. Runtime SSR responses are not processed by this plugin.
Why critical CSS matters
Static and prerendered applications can produce clean, route-specific HTML output that is ready to render immediately. Critical CSS keeps that advantage intact by inlining only the styles needed for the initial viewport, allowing the browser to paint meaningful content before loading the full stylesheet.
This reduces render-blocking CSS, improves perceived performance, and helps keep fast static output truly fast.