项目插件使用举例(自定义 Next.js 插件:静态资源域名替换)

在自研的 Next.js 同构框架里,常常需要把构建后的 HTML 中的静态资源域名替换为目标环境对应的 CDN 域名。一个常见做法是:

在 compiler 的 before-emit / emit 阶段执行 tap 钩子,从 compilation 中获取到 html 内容,匹配到静态资源域名,使用网站域名将其进行替换。

来源整理自:vue3js.cn 面试官系列

简化示例

class ResourceReplacePlugin {
  constructor(opts) {
    this.opts = opts;
    this.resEnvMap = { staging: "//cdn-staging.example.com/", prod: "//cdn.example.com/" };
    this.envMap = { development: "staging", production: "prod" };
  }

  apply(nfes) {
    nfes.server.beforeGenStream.tapAsync("track", (ctx, callback) => {
      const resDomain = this.resEnvMap[this.envMap[this.opts.appConfig.Env]];
      const _list = this.opts.i18n.getDomainList() || [];
      if (_list.length === 0) return callback();
      const exp = new RegExp(`//(${_list.join("|")})/`, "ig");
      ctx.req.__Cover_HTML__ = ctx.html.replace(exp, resDomain);
      callback();
    });

    nfes.server.afterGenStream.tap("track", (ctx) => {
      const resDomain = this.resEnvMap[this.envMap[this.opts.appConfig.Env]];
      const _list = this.opts.i18n.getDomainList() || [];
      if (_list.length === 0) return;
      const exp = new RegExp(`//(${_list.join("|")})/`, "ig");
      ctx.req.__Cover_HTML_AFTER__ = ctx.html.replace(exp, resDomain);
    });
  }
}

module.exports = ResourceReplacePlugin;

补充

补充:在现代 Next.js 中如何用更标准的方式做"出域资源替换"。

Next.js(>= 13 App Router)本身没有暴露官方 plugin API(不像 webpack 有 compiler.hooks),但可以通过以下两种方式做类似的资源替换:

  1. middleware.ts 改写 HTML 输出:
// middleware.ts
import { NextResponse } from "next/server";

const CDN_HOSTS = ["static.dev.example.com", "img.dev.example.com"];
const TARGET = process.env.NEXT_PUBLIC_CDN_HOST!;

export function middleware(req: Request) {
  const url = new URL(req.url);
  if (CDN_HOSTS.some(h => url.hostname === h)) {
    return NextResponse.redirect(new URL(url.pathname + url.search, `https://${TARGET}`));
  }
  return NextResponse.next();
}
  1. 通过自定义 Server(server.js)拦截响应:
const next = require("next");
const { createServer } = require("node:http");
const app = next({ dev: false });

app.prepare().then(() => {
  createServer((req, res) => {
    const handle = app.getRequestHandler();
    handle(req, res).then(() => {
      // 如果需要更细粒度改写,可以再次 chunk 替换 res
    });
  }).listen(3000);
});

实用建议:

  • 优先使用 middleware.ts + NextResponse.rewrite,不必引入自定义 server,能继续享受 edge / serverless 部署能力。
  • 如果一定要在 HTML 生成阶段改写,建议封装成"构建脚本"在 next build 之后运行,避免侵入框架内部。