From 79f061ee58acd89545c9908de1f6a2362646bbec Mon Sep 17 00:00:00 2001 From: neverland Date: Thu, 6 Nov 2025 23:25:15 +0800 Subject: [PATCH 1/2] fix(server): ensure historyApiFallback takes precedence over html fallback --- .../index.test.ts | 18 ++++++++++++++++ .../rsbuild.config.ts | 19 +++++++++++++++++ .../history-api-fallback-rewrites/src/bar.js | 1 + .../history-api-fallback-rewrites/src/foo.js | 1 + .../src/index.js | 1 + ...storyApiFallback.test.ts => index.test.ts} | 0 packages/core/src/server/devMiddlewares.ts | 21 ++++++++++--------- 7 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 e2e/cases/server/history-api-fallback-rewrites/index.test.ts create mode 100644 e2e/cases/server/history-api-fallback-rewrites/rsbuild.config.ts create mode 100644 e2e/cases/server/history-api-fallback-rewrites/src/bar.js create mode 100644 e2e/cases/server/history-api-fallback-rewrites/src/foo.js create mode 100644 e2e/cases/server/history-api-fallback-rewrites/src/index.js rename e2e/cases/server/history-api-fallback/{historyApiFallback.test.ts => index.test.ts} (100%) diff --git a/e2e/cases/server/history-api-fallback-rewrites/index.test.ts b/e2e/cases/server/history-api-fallback-rewrites/index.test.ts new file mode 100644 index 0000000000..2ff980ffba --- /dev/null +++ b/e2e/cases/server/history-api-fallback-rewrites/index.test.ts @@ -0,0 +1,18 @@ +import { expect, rspackTest } from '@e2e/helper'; + +rspackTest( + 'should apply `historyApiFallback.rewrites` correctly', + async ({ page, devOnly }) => { + const rsbuild = await devOnly(); + + await page.goto(`http://localhost:${rsbuild.port}`); + expect(await page.locator('#root').innerHTML()).toEqual('index'); + + // `/baz` should be rewritten to `/foo` + await page.goto(`http://localhost:${rsbuild.port}/baz`); + expect(await page.locator('#root').innerHTML()).toEqual('foo'); + + await page.goto(`http://localhost:${rsbuild.port}/bar`); + expect(await page.locator('#root').innerHTML()).toEqual('bar'); + }, +); diff --git a/e2e/cases/server/history-api-fallback-rewrites/rsbuild.config.ts b/e2e/cases/server/history-api-fallback-rewrites/rsbuild.config.ts new file mode 100644 index 0000000000..eb0998d5ea --- /dev/null +++ b/e2e/cases/server/history-api-fallback-rewrites/rsbuild.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from '@rsbuild/core'; +import { pluginReact } from '@rsbuild/plugin-react'; + +export default defineConfig({ + plugins: [pluginReact()], + source: { + entry: { + index: './src/index.js', + foo: './src/foo.js', + bar: './src/bar.js', + }, + }, + server: { + historyApiFallback: { + index: '/index.html', + rewrites: [{ from: /^\/baz/, to: '/foo.html' }], + }, + }, +}); diff --git a/e2e/cases/server/history-api-fallback-rewrites/src/bar.js b/e2e/cases/server/history-api-fallback-rewrites/src/bar.js new file mode 100644 index 0000000000..3f8002a5c2 --- /dev/null +++ b/e2e/cases/server/history-api-fallback-rewrites/src/bar.js @@ -0,0 +1 @@ +document.querySelector('#root').innerHTML = 'bar'; diff --git a/e2e/cases/server/history-api-fallback-rewrites/src/foo.js b/e2e/cases/server/history-api-fallback-rewrites/src/foo.js new file mode 100644 index 0000000000..030f267236 --- /dev/null +++ b/e2e/cases/server/history-api-fallback-rewrites/src/foo.js @@ -0,0 +1 @@ +document.querySelector('#root').innerHTML = 'foo'; diff --git a/e2e/cases/server/history-api-fallback-rewrites/src/index.js b/e2e/cases/server/history-api-fallback-rewrites/src/index.js new file mode 100644 index 0000000000..d043cd68fb --- /dev/null +++ b/e2e/cases/server/history-api-fallback-rewrites/src/index.js @@ -0,0 +1 @@ +document.querySelector('#root').innerHTML = 'index'; diff --git a/e2e/cases/server/history-api-fallback/historyApiFallback.test.ts b/e2e/cases/server/history-api-fallback/index.test.ts similarity index 100% rename from e2e/cases/server/history-api-fallback/historyApiFallback.test.ts rename to e2e/cases/server/history-api-fallback/index.test.ts diff --git a/packages/core/src/server/devMiddlewares.ts b/packages/core/src/server/devMiddlewares.ts index d68eca331f..56fc1802b5 100644 --- a/packages/core/src/server/devMiddlewares.ts +++ b/packages/core/src/server/devMiddlewares.ts @@ -217,16 +217,7 @@ const applyDefaultMiddlewares = ({ callback(); } - if (buildManager) { - middlewares.push( - getHtmlFallbackMiddleware({ - buildManager, - distPath: context.distPath, - htmlFallback: server.htmlFallback, - }), - ); - } - + // historyApiFallback takes precedence over the default htmlFallback. if (server.historyApiFallback) { middlewares.push( historyApiFallbackMiddleware( @@ -240,6 +231,16 @@ const applyDefaultMiddlewares = ({ } } + if (buildManager) { + middlewares.push( + getHtmlFallbackMiddleware({ + buildManager, + distPath: context.distPath, + htmlFallback: server.htmlFallback, + }), + ); + } + middlewares.push(faviconFallbackMiddleware); return { From 085029fa376c7773bd39bfddb9168a77babc64c7 Mon Sep 17 00:00:00 2001 From: neverland Date: Thu, 6 Nov 2025 23:33:23 +0800 Subject: [PATCH 2/2] docs --- .../docs/en/config/server/history-api-fallback.mdx | 4 ++++ website/docs/en/guide/basic/server.mdx | 14 +++----------- .../docs/zh/config/server/history-api-fallback.mdx | 4 ++++ website/docs/zh/guide/basic/server.mdx | 14 +++----------- 4 files changed, 14 insertions(+), 22 deletions(-) diff --git a/website/docs/en/config/server/history-api-fallback.mdx b/website/docs/en/config/server/history-api-fallback.mdx index 12c9bbe0bb..0fe171446a 100644 --- a/website/docs/en/config/server/history-api-fallback.mdx +++ b/website/docs/en/config/server/history-api-fallback.mdx @@ -7,6 +7,10 @@ When Rsbuild's default [page routing](/guide/basic/server#page-routing) behavior cannot meet your needs, for example, if you want to be able to access `main.html` when accessing `/`, you can achieve this through the `server.historyApiFallback` configuration. +:::tip +The `server.historyApiFallback` option has a higher priority than [server.htmlFallback](/config/server/html-fallback). +::: + ## Example When `server.historyApiFallback` is set to `true`, all HTML GET requests that do not match an actual resource will return `index.html`. This ensures that routing in single-page applications works correctly. diff --git a/website/docs/en/guide/basic/server.mdx b/website/docs/en/guide/basic/server.mdx index ff7ad9d235..07a17cc296 100644 --- a/website/docs/en/guide/basic/server.mdx +++ b/website/docs/en/guide/basic/server.mdx @@ -53,18 +53,10 @@ export default { ### Fallback behavior -By default, requests that meet the following conditions fall back to `index.html` when the corresponding resource is not found: +If a request meets the following conditions but no corresponding static asset is found, [server.htmlFallback](/config/server/html-fallback) will be triggered and will fall back to `index.html` by default: -- The request is a `GET` or `HEAD` request -- The request accepts `text/html` (the request header accept type is `text/html` or `*/*`) - -```ts title="rsbuild.config.ts" -export default { - server: { - htmlFallback: 'index', - }, -}; -``` +- The request method is `GET` or `HEAD` +- The `Accept` header contains `text/html` (for example, `text/html` or `*/*`) ### Custom fallback behavior diff --git a/website/docs/zh/config/server/history-api-fallback.mdx b/website/docs/zh/config/server/history-api-fallback.mdx index 895716c59a..d2c7937218 100644 --- a/website/docs/zh/config/server/history-api-fallback.mdx +++ b/website/docs/zh/config/server/history-api-fallback.mdx @@ -7,6 +7,10 @@ 当 Rsbuild 默认的 [页面路由](/guide/basic/server#page-routing) 行为无法满足你的需求时,例如,希望在访问 `/` 时可以访问 `main.html` ,你可以通过 `server.historyApiFallback` 配置项来实现这个功能。 +:::tip +`server.historyApiFallback` 的优先级高于 [server.htmlFallback](/config/server/html-fallback)。 +::: + ## 示例 将 `server.historyApiFallback` 设置为 `true` 时,所有未匹配到实际资源的 HTML GET 请求都会返回 `index.html`,从而保证单页应用的路由能够正常工作。 diff --git a/website/docs/zh/guide/basic/server.mdx b/website/docs/zh/guide/basic/server.mdx index 1f8da60271..a78fcea88f 100644 --- a/website/docs/zh/guide/basic/server.mdx +++ b/website/docs/zh/guide/basic/server.mdx @@ -53,18 +53,10 @@ export default { ### Fallback 行为 -当请求满足以下条件且未找到对应资源时,会被 `server.htmlFallback` 处理,默认会回退到 index.html。 +当请求满足以下条件且未找到对应的静态资源时,[server.htmlFallback](/config/server/html-fallback) 将生效,默认会回退到 `index.html`: -- 当前请求是 GET 或 HEAD 请求 -- 当前请求头接受 `text/html` (请求头 accept 类型为 `text/html` 或 `*/*`) - -```ts title="rsbuild.config.ts" -export default { - server: { - htmlFallback: 'index', - }, -}; -``` +- 请求方法为 `GET` 或 `HEAD` +- 请求头中的 `Accept` 包含 `text/html`(即类型为 `text/html` 或 `*/*`) ### 自定义 Fallback 行为