Open with AI
Next.js and other hosts
stillsmith compiles your scenes with Vite. That does not mean your project has
to use Vite: when there’s no vite.config to merge, stillsmith synthesizes one
from the signals every project already has. A Next.js, CRA, or plain-webpack
app works without adding any build configuration.
The ladder
Section titled “The ladder”At startup, stillsmith resolves how scenes get compiled, in order:
vite:points at a config → load it and merge into it.- A
vite.config.*is found (walking up from your stillsmith config) → same. - Neither, or
vite: false→ synthesize a config from:- tsconfig
paths— your@/*aliases,extendschains included postcss.config.*— Tailwind (v3, and v4 via@tailwindcss/postcss) applies with no plugins, because Vite picks PostCSS up automatically.envfiles — public variables (NEXT_PUBLIC_*,REACT_APP_*) become defines, soprocess.env.NEXT_PUBLIC_API_URLworks in the browser
- tsconfig
Whichever tier runs, every command says so — synthesis is never silent:
stillsmith: host next — synthesized config (aliases: tsconfig paths; shims: next/image, …)vite: false forces synthesis even when a vite.config exists — useful in a
monorepo where the config stillsmith would find belongs to a different app.
Components import modules that only mean something inside their
meta-framework’s runtime — next/image expects Next’s optimizer, next/font
expects a compile-time transform. stillsmith ships shims: faithful-for-a-
screenshot stand-ins, applied automatically when the host is detected. A Next
project gets:
| Module | What the shim does |
|---|---|
next/image |
A plain <img>; handles static-import objects and fill. Original asset pixels — which is what a screenshot wants anyway. |
next/link |
An <a>, object hrefs stringified. |
next/navigation, next/router |
Inert router; usePathname and friends read a seedable route state (defaults to /). |
next/font/google |
Loads the real font from Google Fonts — any family, not a fixed list — and capture waits for it before the shutter. |
next/head, next/dynamic, next/script |
Portal into document.head; React.lazy + Suspense; a plain <script>. |
server-only, next/headers, next/cache |
Throw on import, with a recipe. |
That last row is deliberate: scenes are client renders, and stillsmith does not emulate a server-component runtime. A server-only component fails loudly — extract the presentational child and shoot that.
Override or disable any shim per-specifier, or all of them:
shims: { "next/font/google": "./my-font-shim.ts", // project-local replacement "next/script": false, // resolve the real module instead},// or: shims: falseRecreate what your layout provides
Section titled “Recreate what your layout provides”Scenes render your component — not your app/layout.tsx. Anything the layout
contributes (font variables, theme classes, providers) has to come from the
config’s wrapper, same as any other provider:
import { defineConfig } from "@stillsmith/capture/react";import { Geist, Geist_Mono } from "next/font/google";import "@/app/globals.css";
const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });
export default defineConfig({ scenes: ["src/**/*.scene.tsx"], wrapper: ({ children }) => ( <div className={`${geistSans.variable} ${geistMono.variable} antialiased`}> {children} </div> ), // …presets, targets});This mirrors the className your layout puts on <body>, so Tailwind theme
tokens like font-sans resolve to the same fonts your app ships.
Tours against your running app
Section titled “Tours against your running app”Guided-tour authoring drives your real app, and that never needed your build
at all — only a URL. Point appUrl at any running dev server:
appUrl: "http://localhost:3000", // e.g. `next dev`Tour preview and authoring open that origin directly. Scene capture still runs
through stillsmith’s own server; appUrl only affects app pages.
Limitations
Section titled “Limitations”Synthesis reproduces your build’s configuration, not its compiler plugins:
- Compiler-transform CSS-in-JS — vanilla-extract’s webpack plugin,
styled-components’ SWC transform — doesn’t apply. If a component depends on
one, give stillsmith a small dedicated
vite.configwith the Vite equivalent of that plugin (tier 1 is always available). next/font/localresolves itssrcwith a compile-time transform a runtime shim can’t replicate; it falls back to the system stack and says so. Override it viashims:if the exact face matters.- Monorepo note:
create-next-appwrites its ownpnpm-workspace.yamlinto the new app. Inside an existing pnpm workspace, delete it — a nested workspace root confuses pnpm, stillsmith aside.