diff --git a/packages/docusaurus/src/commands/serve.ts b/packages/docusaurus/src/commands/serve.ts index aea422d16c20..1bfdf226f983 100644 --- a/packages/docusaurus/src/commands/serve.ts +++ b/packages/docusaurus/src/commands/serve.ts @@ -12,11 +12,19 @@ import logger from '@docusaurus/logger'; import {DEFAULT_BUILD_DIR_NAME} from '@docusaurus/utils'; import serveHandler from 'serve-handler'; import openBrowser from 'react-dev-utils/openBrowser'; +import {applyTrailingSlash} from '@docusaurus/utils-common'; import {loadSiteConfig} from '../server/config'; import {build} from './build'; import {getHostPort, type HostPortOptions} from '../server/getHostPort'; import type {LoadContextParams} from '../server/site'; +function redirect(res: http.ServerResponse, location: string) { + res.writeHead(302, { + Location: location, + }); + res.end(); +} + export type ServeCLIOptions = HostPortOptions & Pick & { dir?: string; @@ -62,15 +70,24 @@ export async function serve( const server = http.createServer((req, res) => { // Automatically redirect requests to /baseUrl/ if (!req.url?.startsWith(baseUrl)) { - res.writeHead(302, { - Location: baseUrl, - }); - res.end(); + redirect(res, baseUrl); + return; + } + + // We do the redirect ourselves for a good reason + // server-handler is annoying and won't include /baseUrl/ in redirects + const normalizedUrl = applyTrailingSlash(req.url, {trailingSlash, baseUrl}); + if (req.url !== normalizedUrl) { + redirect(res, normalizedUrl); return; } // Remove baseUrl before calling serveHandler, because /baseUrl/ should // serve /build/index.html, not /build/baseUrl/index.html (does not exist) + // Note server-handler is really annoying here: + // - no easy way to do rewrites such as "/baseUrl/:path" => "/:path" + // - no easy way to "reapply" the baseUrl to the redirect "Location" header + // See also https://github.com/facebook/docusaurus/pull/10090 req.url = req.url.replace(baseUrl, '/'); serveHandler(req, res, {