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, content is 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 -D

Replace the Waku scripts with the Fumapress CLI:

package.json
{
  "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:

vite.config.ts
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.ts if present, it is stale output of Waku's route typegen (disabled by Fumapress) and its waku/router type imports no longer resolve.
  • Remove the resolve.dedupe: ["fumadocs-ui"] workaround, press() now adds detected framework packages to resolve.dedupe itself.

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:

BeforeAfter
.layouts({ root })renderRoot
.layouts({ page })renderPage
.layouts({ notFound })renderNotFound
.layouts({ defaultProps })defaultLayoutProps
press.config.tsx
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

BeforeAfter
ConfigContextAppShape
ServerPlugin / ServerPluginOptionPressPlugin / PressPluginOption
ConfigBuilderConfigUtils
Layoutsrender* / defaultLayoutProps
i18n on context shapelang 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:

BeforeAfter
ctx.layoutsctx.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-bodyctx.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

On this page