Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions packages/docusaurus/src/commands/serve/requestHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import serveHandler from 'serve-handler';
import {applyTrailingSlash} from '@docusaurus/utils-common';
import type http from 'node:http';
import type {DocusaurusConfig} from '@docusaurus/types';

type ServeRequestHandler = (
req: http.IncomingMessage,
res: http.ServerResponse,
) => Promise<void>;

export function createServeRequestHandler({
outDir,
baseUrl,
trailingSlash,
}: {outDir: string} & Pick<
DocusaurusConfig,
'baseUrl' | 'trailingSlash'
>): ServeRequestHandler {
return async (req, res) => {
// Automatically redirect requests to /baseUrl/
if (!req.url?.startsWith(baseUrl)) {
redirect(res, baseUrl);
return;
}

// We do the redirect ourselves for a good reason
// server-handler is annoying and won't include /baseUrl/ in redirects
// See https://github.com/meta/docusaurus/issues/10078#issuecomment-2084932934
if (baseUrl !== '/') {
// Not super robust, but should be good enough for our use case
// See https://github.com/meta/docusaurus/pull/10090
const looksLikeAsset = !!req.url.match(/\.[a-z\d]{1,4}$/i);
if (!looksLikeAsset) {
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/meta/docusaurus/pull/10090
req.url = req.url.replace(baseUrl, '/');

await serveHandler(req, res, {
cleanUrls: true,
public: outDir,
trailingSlash,
directoryListing: false,
});
};
}

function redirect(res: http.ServerResponse, location: string) {
res.writeHead(302, {
Location: location,
});
res.end();
}
54 changes: 4 additions & 50 deletions packages/docusaurus/src/commands/serve/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,15 @@ import http from 'node:http';
import path from 'node:path';
import logger from '@docusaurus/logger';
import {DEFAULT_BUILD_DIR_NAME} from '@docusaurus/utils';
import serveHandler from 'serve-handler';
import {applyTrailingSlash} from '@docusaurus/utils-common';
import {realpath} from '@docusaurus/fs';
import openBrowser from '../utils/openBrowser/openBrowser';
import {loadSiteConfig} from '../../server/config';
import {build} from '../build/build';
import {getHostPort, type HostPortOptions} from '../../server/getHostPort';
import {listenToServer} from './listenToServer';
import {createServeRequestHandler} from './requestHandler';
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<LoadContextParams, 'config'> & {
dir?: string;
Expand Down Expand Up @@ -64,47 +56,9 @@ export async function serve(

const servingUrl = `http://${host}:${port}`;

const server = http.createServer((req, res) => {
// Automatically redirect requests to /baseUrl/
if (!req.url?.startsWith(baseUrl)) {
redirect(res, baseUrl);
return;
}

// We do the redirect ourselves for a good reason
// server-handler is annoying and won't include /baseUrl/ in redirects
// See https://github.com/meta/docusaurus/issues/10078#issuecomment-2084932934
if (baseUrl !== '/') {
// Not super robust, but should be good enough for our use case
// See https://github.com/meta/docusaurus/pull/10090
const looksLikeAsset = !!req.url.match(/\.[a-z\d]{1,4}$/i);
if (!looksLikeAsset) {
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/meta/docusaurus/pull/10090
req.url = req.url.replace(baseUrl, '/');

serveHandler(req, res, {
cleanUrls: true,
public: outDir,
trailingSlash,
directoryListing: false,
});
});
const server = http.createServer(
createServeRequestHandler({outDir, baseUrl, trailingSlash}),
);

const url = servingUrl + baseUrl;
logger.success`Serving path=${buildDir} directory at: url=${url}`;
Expand Down
Loading