Migrate to v1
Breaking changes from the v1 redesign.
Overview
v1 changes two things:
- Fumapress ships its own CLI and manages Waku.js as an internal dependency, your app no longer installs Waku itself.
- The config API is reshaped: layouts move onto the config object,
contentis required, and plugin types are renamed.
Update to v1:
npm i fumapress@latest.layouts() still works as a compat layer, but prefer the new options below.
CLI & Dependencies
Waku.js is a dependency of Fumapress now, your app no longer installs it:
npm uninstall waku react-server-dom-webpack
npm i vite -DReplace the Waku scripts with the Fumapress CLI:
{
"scripts": {
"dev": "waku dev",
"build": "waku build",
"start": "waku start",
"dev": "fumapress dev",
"build": "fumapress build",
"start": "fumapress start"
}
}Rename waku.config.ts to vite.config.ts. It is a plain Vite config now, use defineConfig from vite and move the contents of the vite field to the top level:
import { defineConfig } from "waku/config";
import { defineConfig } from "vite";
import press from "fumapress/vite";
import { fumadocsMdx } from "fumadocs-mdx/vite";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
vite: {
plugins: [press(), fumadocsMdx(), tailwindcss()],
},
plugins: [press(), fumadocsMdx(), tailwindcss()],
});Waku options (basePath, srcDir, distDir, privateDir, rscBase, and the deployment adapter) moved into the press() plugin options, see Vite Plugin.
Two clean-ups left:
- Delete
src/pages.gen.tsif present, it is stale output of Waku's route typegen (disabled by Fumapress) and itswaku/routertype imports no longer resolve. - Remove the
resolve.dedupe: ["fumadocs-ui"]workaround,press()now adds detected framework packages toresolve.dedupeitself.
Custom server entries (src/waku.server.tsx) keep using Waku APIs directly, install waku yourself in that case, see Server Entry.
Content
loader is removed. Pass sources via content:
// before
defineConfig({ loader: docs.toFumadocsSource() });
// after
defineConfig({ content: docs.toFumadocsSource() });Layouts
Move layout options onto the config object:
| Before | After |
|---|---|
.layouts({ root }) | renderRoot |
.layouts({ page }) | renderPage |
.layouts({ notFound }) | renderNotFound |
.layouts({ defaultProps }) | defaultLayoutProps |
import { defineConfig } from "fumapress";
import { createDocsLayoutPage } from "fumapress/layouts/docs";
// before
defineConfig({ content }).layouts({
defaultProps() {
return { links: [...] };
},
page: createDocsLayoutPage(),
});
// after
const DocsLayout = createDocsLayoutPage<typeof config.$context>();
const config = defineConfig({
content,
defaultLayoutProps: {
links: [...],
},
renderPage: (props) => <DocsLayout {...props} />,
});
export default config;Prefer wrapping the layout in renderPage instead of passing create*LayoutPage() directly, this avoids TypeScript inference issues.
defaultLayoutProps can be a plain object, or a function when you need lang / app context.
See Layouts for switching layouts and shared props.
Plugins & Adapters
.usePlugins() / .useAdapters() are removed. Use .plugins() / .adapters():
defineConfig({ content }).plugins(flexsearchPlugin()).adapters(fumadocsMdx());When you need getPressContext, export it from config.utils() so it is typed to your app:
const config = defineConfig({ content });
export const { getPressContext } = config.utils();
export default config;Plugin & Adapter Authors
The remaining changes only affect custom plugins and adapters.
Types
| Before | After |
|---|---|
ConfigContext | AppShape |
ServerPlugin / ServerPluginOption | PressPlugin / PressPluginOption |
ConfigBuilder | ConfigUtils |
Layouts | render* / defaultLayoutProps |
i18n on context shape | lang on AppShape |
$context is an AppShape (page / meta / lang / source).
// before
import type { ConfigContext, ServerPlugin } from "fumapress";
export function myPlugin<C extends ConfigContext>(): ServerPlugin<C> {
// ...
}
// after
import type { AppShape, PressPlugin } from "fumapress";
export function myPlugin<C extends AppShape>(): PressPlugin<C> {
// ...
}App Context
Helpers that used to take ctx as an argument are methods on AppContext:
| Before | After |
|---|---|
ctx.layouts | ctx.renderRoot / ctx.renderPage / ... |
renderPageMeta(page, ctx) | ctx.renderPageMeta(page) |
renderRootMeta(ctx) | ctx.renderRootMeta() |
getCreationDate(ctx, page) | ctx.getPageCreatedAt(page) |
getLastModifiedDate(ctx, page) | ctx.getPageLastModified(page) |
getGitHubFileUrl(ctx, path) | ctx.getFileUrl(path) |
renderToc(ctx, page) | ctx.getPageToc(page) |
renderBody / core:render-body | ctx.getPageBody(page) → { node } |
data["core:page-meta"] | ctx.interceptPageMeta(...) |
Standalone exports like baseLayoutProps, getCreationDate, renderBody, and TransformChildren are no longer public.
Layout transformers are renamed from renderers to transformers:
// before
data["core:docs-layout"].renderers;
// after
data["core:docs-layout"].transformers;Adapters
core:render-body is renamed to core:get-body, and returns { node } instead of a bare React node:
// before
async "core:render-body"(page) {
return <MyPage />;
}
// after
async "core:get-body"(page) {
return { node: <MyPage /> };
}Plugins
Plugins that appended OG tags (or other meta) via data["core:page-meta"] should use interceptPageMeta:
this.interceptPageMeta(({ page, next }) => (
<>
{next()}
<meta property="og:image" content={...} />
</>
));Last updated on
