fix(build): include assets in standalone output

With output: "standalone", next build writes server.js under
.next/standalone but leaves generated static/public assets in the
project root, so starting the standalone server directly (e.g. via PM2)
404s on JS/CSS/font/favicon requests and /login stays stuck loading.
Add a postbuild step that copies .next/static and public into the
standalone directory, skipping the workspace-traced CLI build which
already copies its own assets.
This commit is contained in:
DaDecky
2026-08-05 10:31:34 +07:00
committed by decolua
parent 0648e9e420
commit 786b3013ba
3 changed files with 93 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { mkdtempSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { copyStandaloneAssets } from "../../scripts/copy-standalone-assets.mjs";
function createBuildFixture(distDir) {
const projectRoot = mkdtempSync(join(tmpdir(), "9router-standalone-assets-"));
const buildRoot = join(projectRoot, distDir);
mkdirSync(join(buildRoot, "standalone"), { recursive: true });
mkdirSync(join(buildRoot, "static", "chunks"), { recursive: true });
mkdirSync(join(projectRoot, "public"), { recursive: true });
writeFileSync(join(buildRoot, "static", "chunks", "app.js"), "static asset");
writeFileSync(join(projectRoot, "public", "favicon.svg"), "public asset");
return projectRoot;
}
describe("standalone build assets", () => {
it("copies static and public assets into the default standalone layout", () => {
const projectRoot = createBuildFixture(".next");
copyStandaloneAssets({ projectRoot, distDir: ".next" });
expect(readFileSync(join(projectRoot, ".next", "standalone", ".next", "static", "chunks", "app.js"), "utf8"))
.toBe("static asset");
expect(readFileSync(join(projectRoot, ".next", "standalone", "public", "favicon.svg"), "utf8"))
.toBe("public asset");
});
it("uses a custom Next dist directory", () => {
const projectRoot = createBuildFixture(".next-cli-build");
copyStandaloneAssets({ projectRoot, distDir: ".next-cli-build" });
expect(readFileSync(join(projectRoot, ".next-cli-build", "standalone", ".next-cli-build", "static", "chunks", "app.js"), "utf8"))
.toBe("static asset");
});
it("does not modify workspace-traced CLI builds", () => {
const projectRoot = createBuildFixture(".next-cli-build");
const previousMode = process.env.NEXT_TRACING_ROOT_MODE;
process.env.NEXT_TRACING_ROOT_MODE = "workspace";
try {
copyStandaloneAssets({ projectRoot, distDir: ".next-cli-build" });
} finally {
if (previousMode === undefined) delete process.env.NEXT_TRACING_ROOT_MODE;
else process.env.NEXT_TRACING_ROOT_MODE = previousMode;
}
expect(() => readFileSync(join(projectRoot, ".next-cli-build", "standalone", ".next-cli-build", "static", "chunks", "app.js")))
.toThrow();
});
});