You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

手动构建React SSR应用(非NextJS)配置自定义基础URL路径

Great question—this is a common pitfall when setting up SSR with custom base paths, especially when you're not using CRA or Next.js which handle a lot of this out of the box. Let's walk through how to fix the 404s and duplicate path issues step by step:

1. Create a Centralized Base Path Constant

First, avoid hardcoding the base path everywhere—define it once in a config file and reference it across your app. This ensures consistency and makes it easy to update later.

// src/config.js
// Use an environment variable for flexibility, fall back to root path
export const BASE_PATH = process.env.BASE_PATH || "/";

// *Critical note:* Ensure BASE_PATH starts with a / and doesn’t end with one (e.g., "/a/b"), or is an empty string for the root path.

Inject this variable into both your client and server Webpack builds by adding it to the DefinePlugin:

// webpack.client.js and webpack.server.js
const webpack = require("webpack");
const { BASE_PATH } = require("./src/config");

module.exports = {
  // ... other config
  plugins: [
    new webpack.DefinePlugin({
      "process.env.BASE_PATH": JSON.stringify(BASE_PATH),
    }),
  ],
};
2. Configure React Router for Base Path

Both client-side BrowserRouter and server-side StaticRouter need to know the base path, and you’ll need to clean up the incoming request URL on the server.

Client-Side Router

// src/client/index.js
import { BrowserRouter } from "react-router-dom";
import { BASE_PATH } from "./config";

ReactDOM.hydrate(
  <BrowserRouter basename={BASE_PATH}>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);

Server-Side Router

The server receives requests with the full base path (e.g., /a/b/page1), so you need to strip the base path before passing it to StaticRouter:

// src/server/render.js
import { StaticRouter } from "react-router-dom/server";
import { BASE_PATH } from "../config";

export function render(req, res) {
  // Strip the base path from the request URL to match your app's route definitions
  const strippedUrl = req.url.replace(new RegExp(`^${BASE_PATH}`), "") || "/";

  const context = {};
  const appHtml = ReactDOMServer.renderToString(
    <StaticRouter
      location={strippedUrl}
      basename={BASE_PATH}
      context={context}
    >
      <App />
    </StaticRouter>
  );

  // ... generate final HTML (see step 4)
}
3. Fix Webpack Asset Paths (Solve 404s)

This is the most critical step for fixing missing styles/bundles. Configure Webpack’s publicPath to match your base path so assets are served correctly.

Client Webpack Config

// webpack.client.js
const path = require("path");
const { BASE_PATH } = require("./src/config");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  entry: "./src/client/index.js",
  output: {
    filename: "client.bundle.js",
    path: path.resolve(__dirname, "dist/client"),
    // Set publicPath to your base path + trailing slash
    publicPath: `${BASE_PATH}/`,
  },
  plugins: [
    new MiniCssExtractPlugin({ filename: "styles.css" }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
      // For static assets like images
      {
        test: /\.(png|jpeg|svg)$/,
        loader: "file-loader",
        options: {
          outputPath: "assets",
          publicPath: `${BASE_PATH}/assets/`,
        },
      },
    ],
  },
};

Server Webpack Config

For server-side bundling, ensure static assets are emitted to the client’s dist folder so they’re accessible via the base path:

// webpack.server.js
const path = require("path");
const { BASE_PATH } = require("./src/config");

module.exports = {
  entry: "./src/server/index.js",
  output: {
    filename: "server.bundle.js",
    path: path.resolve(__dirname, "dist/server"),
    libraryTarget: "commonjs2",
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["css-loader/locals"], // No need to emit CSS for server
      },
      {
        test: /\.(png|jpeg|svg)$/,
        loader: "file-loader",
        options: {
          outputPath: "../client/assets", // Emit to client dist folder
          publicPath: `${BASE_PATH}/assets/`,
          emitFile: true,
        },
      },
    ],
  },
};

When generating the server-rendered HTML, reference assets using the base path to avoid broken links:

// src/server/render.js
import { BASE_PATH } from "../config";

function generateHtml(appHtml) {
  return `
    <!DOCTYPE html>
    <html>
      <head>
        <title>SSR App</title>
        <link rel="stylesheet" href="${BASE_PATH}/styles.css">
      </head>
      <body>
        <div id="root">${appHtml}</div>
        <script src="${BASE_PATH}/client.bundle.js"></script>
      </body>
    </html>
  `;
}
5. Configure Server Static File Serving

If you’re using Express, map the base path to your client’s dist folder so static assets are served correctly:

// src/server/index.js
import express from "express";
import path from "path";
import { BASE_PATH } from "../config";
import { render } from "./render";

const app = express();

// Serve client assets from the base path
app.use(BASE_PATH, express.static(path.resolve(__dirname, "../../dist/client")));

// Handle all routes with SSR
app.get("*", render);

app.listen(3000, () => console.log("Server running on http://localhost:3000"));
6. Test the Setup

Run your app with a custom base path by setting the environment variable:

BASE_PATH="/a/b" npm run build && npm run start
  • Visit http://localhost:3000/a/b (homepage)
  • Visit http://localhost:3000/a/b/page1 (page1)
  • Check the network tab: assets should load from /a/b/client.bundle.js and /a/b/styles.css with 200 status codes
  • Client-side navigation should keep the base path intact (no duplicate /a/b/a/b/ paths)

内容的提问来源于stack exchange,提问作者se22as

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 11:47:51