SSR framework for Svelte 5 + Bun with islands-based selective hydration
On this page
Tailwind
Drive Tailwind v4 with its Node API. Mochi ships an opt-in helper at mochi-framework/tailwind that compiles your input CSS at server startup and re-runs on file changes in dev. Then import the generated file from any .svelte and Mochi’s CSS-import bundler links it scoped to the page.
Setup
- Install Tailwind alongside its Node and scanner packages:
bun add tailwindcss @tailwindcss/node @tailwindcss/oxide- Write an input CSS that imports the layers you want and tells Tailwind where to scan:
/* file: src/styles/app.css */
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/utilities.css';
@source './*.svelte';- Call
setupTailwindat module scope insrc/index.ts, beforeMochi.serve(). Top-levelawaitensures the generated CSS exists for both the build CLI and the dev server:
// file: src/index.ts
import { Mochi } from 'mochi-framework';
import { setupTailwind } from 'mochi-framework/tailwind';
await setupTailwind({
input: './src/styles/app.css',
output: './src/styles/app.generated.css',
minify: process.env.NODE_ENV !== 'development',
});
await Mochi.serve({
routes: {
'/': Mochi.page('./src/Home.svelte'),
},
});importthe generated file from any.sveltethat uses Tailwind classes:
<!-- file: src/Home.svelte -->
<script>
import './styles/app.generated.css';
</script>
<button class="rounded-md bg-emerald-600 px-3 py-1.5 text-white hover:bg-emerald-700">Click</button>- Add
app.generated.cssto.gitignore— it is a build artifact.
The bundler strips the import from the JS bundle and serves the CSS at /_mochi/import-css/<hash>.css. The <link> is added to every page that transitively imports it. Pages that do not reference it ship no Tailwind. See CSS imports.
setupTailwind options
| Option | Default | Meaning |
|---|---|---|
input | — | Path to the input CSS (@imports and @source rules). |
output | — | Path where the generated CSS is written, stable for import. |
base | directory of input | Anchors @source patterns. |
minify | false | Minify the optimised output. Set from process.env.NODE_ENV. |
Dev rebuilds
In development, setupTailwind subscribes to file:change on mochiEvents and rebuilds on .svelte / .ts / .js / .html / .md / .svx / .css changes. The resulting write goes through Mochi’s CSS fast-path — a stylesheet swap, not a full SSR rebuild. The watcher attaches only when process.env.NODE_ENV === 'development'.
Production builds
setupTailwind static-imports @tailwindcss/oxide, a native module. If your production runtime image uses a different libc than the install image, the binding installed at build time fails to load at runtime and the server crashes at startup with Cannot find native binding.
Generate the CSS at build time and dynamic-import the helper so production never loads oxide:
// file: src/index.ts
if (process.env.NODE_ENV === 'development') {
const { setupTailwind } = await import('mochi-framework/tailwind');
await setupTailwind({
input: './src/styles/app.css',
output: './src/styles/app.generated.css',
});
}Pair it with a prebuild script that compiles the CSS ahead of mochi-framework build:
// file: scripts/prebuild.ts
import { compileTailwind } from 'mochi-framework/tailwind';
await compileTailwind({
input: './src/styles/app.css',
output: './src/styles/app.generated.css',
minify: true,
});"scripts": {
"build": "bun scripts/prebuild.ts && mochi-framework build"
}Preflight and resets
The example imports tailwindcss/utilities.css without layer(utilities). If your shell’s CSS ships an unlayered universal reset, wrapping utilities in layer(utilities) lets the unlayered reset clobber .p-6, .mt-2, and so on, because unlayered styles beat layered ones in the cascade.
The example also skips Tailwind’s preflight so the stylesheet does not reset unrelated UI. The cost is that user-agent defaults leak through, so <button> keeps its rounded macOS pill shape. Add a small reset:
/* file: src/styles/app.css */
button {
appearance: none;
background: transparent;
border: 0;
font: inherit;
color: inherit;
cursor: pointer;
}To opt back in, @import 'tailwindcss/preflight.css' layer(base);.
See it in action
Live demos showing key concepts from this page