diff --git a/.github/workflows/e2e-next-faustwp-example.yml b/.github/workflows/e2e-next-faustwp-example.yml index 540b30464..706a03bbe 100644 --- a/.github/workflows/e2e-next-faustwp-example.yml +++ b/.github/workflows/e2e-next-faustwp-example.yml @@ -58,3 +58,8 @@ jobs: working-directory: e2e-app run: | npm run build + # next build alone doesn't catch errors that only happen when the + # production server renders a page on demand. + - name: Serve and load a page rendered on demand + run: | + bash scripts/smoke-test-example.sh e2e-app diff --git a/docs/reference/with-faust/index.md b/docs/reference/with-faust/index.md index c9e7122ec..0765f624d 100644 --- a/docs/reference/with-faust/index.md +++ b/docs/reference/with-faust/index.md @@ -10,7 +10,7 @@ The `withFaust` helper function is used to merge your `next.config.js` config wi To use `withFaust`, create a `next.config.js` file in your project root with the following content: ```js title="next.config.js" -const { withFaust } = require("@faustjs/next"); +const { withFaust } = require("@faustwp/core"); /** * @type {import('next').NextConfig} @@ -23,7 +23,7 @@ If you already have a `next.config.js` file, you can use the first argument of ` Take the following example, where we have a custom `next.config.js` handling a rewrite: ```js title="next.config.js" -const { withFaust } = require("@faustjs/next"); +const { withFaust } = require("@faustwp/core"); const nextConfig = { async rewrites() { @@ -42,6 +42,22 @@ const nextConfig = { module.exports = withFaust(nextConfig); ``` +## What `withFaust` changes + +`withFaust` keeps your config and adds: + +- A redirect that sends preview links to the preview page. +- An `x-using: faust` header, and `poweredByHeader: false`. +- `@faustwp/core` and `@faustwp/blocks` in `transpilePackages`, so Next.js bundles them into your pages. Without this, Turbopack production builds return a 500 error ("NextRouter was not mounted") for pages rendered on demand. + +If you don't use `withFaust`, add the packages yourself: + +```js title="next.config.js" +module.exports = { + transpilePackages: ["@faustwp/core", "@faustwp/blocks"], +}; +``` + ## Additional Options `withFaust` also accepts a second argument for `withFaustConfig`. `withFaustConfig` represented as a TypeScript interface looks like this: @@ -56,7 +72,7 @@ interface WithFaustConfig { For example, if you want to change the default preview destination from `/preview` to `/previews`, you can do so by passing the following to `withFaust`: ```js title="next.config.js" -const { withFaust } = require("@faustjs/next"); +const { withFaust } = require("@faustwp/core"); const nextConfig = {}; diff --git a/packages/faustwp-core/src/config/withFaust.ts b/packages/faustwp-core/src/config/withFaust.ts index 918a411dd..84e8d8a80 100644 --- a/packages/faustwp-core/src/config/withFaust.ts +++ b/packages/faustwp-core/src/config/withFaust.ts @@ -11,6 +11,14 @@ export interface WithFaustConfig { previewDestination?: string; } +/** + * Faust packages to bundle into the app. In production, Turbopack loads + * packages from node_modules outside the app's bundle, so their components + * read a separate copy of Next.js's router context and throw "NextRouter was + * not mounted" on pages rendered on demand. Bundling them keeps one copy. + */ +export const FAUST_TRANSPILE_PACKAGES = ['@faustwp/core', '@faustwp/blocks']; + export async function createRedirects( nextConfig?: NextConfig, redirectFn?: NextConfig['redirects'], @@ -105,5 +113,12 @@ export function withFaust( nextConfig.headers = () => addHeaders(nextConfig, existingHeaders); nextConfig.poweredByHeader = false; + nextConfig.transpilePackages = Array.from( + new Set([ + ...(nextConfig.transpilePackages ?? []), + ...FAUST_TRANSPILE_PACKAGES, + ]), + ); + return nextConfig; } diff --git a/packages/faustwp-core/tests/config/withFaust.test.ts b/packages/faustwp-core/tests/config/withFaust.test.ts index 4c6912ba6..389d86bd2 100644 --- a/packages/faustwp-core/tests/config/withFaust.test.ts +++ b/packages/faustwp-core/tests/config/withFaust.test.ts @@ -104,4 +104,18 @@ describe('withFaust', () => { ] `); }); + + test('it bundles the Faust packages', () => { + expect(withFaust({}).transpilePackages).toEqual([ + '@faustwp/core', + '@faustwp/blocks', + ]); + }); + + test('it keeps transpilePackages from the user config', () => { + expect( + withFaust({ transpilePackages: ['my-lib', '@faustwp/core'] }) + .transpilePackages, + ).toEqual(['my-lib', '@faustwp/core', '@faustwp/blocks']); + }); }); diff --git a/scripts/smoke-test-example.sh b/scripts/smoke-test-example.sh new file mode 100755 index 000000000..7834bda89 --- /dev/null +++ b/scripts/smoke-test-example.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Serve a built example app and check that a page rendered on demand loads. +# `next build` alone doesn't catch errors that only happen when a page is +# rendered by the production server. +# +# Usage: scripts/smoke-test-example.sh [path] +set -euo pipefail + +APP_DIR="$1" +PAGE_PATH="${2:-/sample-page}" +PORT="${PORT:-3999}" + +cd "$APP_DIR" +npx next start -p "$PORT" >next-start.log 2>&1 & +SERVER_PID=$! +trap 'kill "$SERVER_PID" 2>/dev/null || true' EXIT + +for _ in $(seq 1 30); do + curl -s -o /dev/null "http://localhost:$PORT/" && break + sleep 1 +done + +STATUS=$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:$PORT$PAGE_PATH") +echo "GET $PAGE_PATH -> $STATUS" + +if [ "$STATUS" != "200" ]; then + echo "Expected 200. Server log:" + cat next-start.log + exit 1 +fi