@scalar/api-reference-react is a documentation SPA reachable only from one admin tab, and it renders nothing usable on the server, yet it was compiled into the server graph for an SSR pass whose output is discarded. It is a client component already, so next/dynamic with ssr: false is allowed in place and needs no restructuring. @modelcontextprotocol/sdk is only reachable through /api/mcp/[transport], so it moves to serverExternalPackages. Honest accounting: this is an image-size and architecture change, not a measured memory win. Idle landed at 89.4 MiB against 90.3/91.1/100.5 for the previous build — the better end of that spread, but inside it, so on one sample it cannot be claimed as a reduction. What is deterministic is image size, 401 -> 398 MB. Kept because a docs SPA should not be server-rendered regardless of what the memory number does. pnpm typecheck and lint clean; pnpm test 1794/1795 with the known Testcontainers pi-hole flake, which passes 9/9 in isolation.
125 lines
4.2 KiB
TypeScript
125 lines
4.2 KiB
TypeScript
// Importing env files here to validate on build
|
|
import path from "node:path";
|
|
|
|
import "@homarr/auth/env";
|
|
import "@homarr/core/infrastructure/db/env";
|
|
import "@homarr/common/env";
|
|
import "@homarr/core/infrastructure/logs/env";
|
|
import "@homarr/docker/env";
|
|
|
|
import type { NextConfig } from "next";
|
|
import createNextIntlPlugin from "next-intl/plugin";
|
|
|
|
// Package path does not work... so we need to use relative path
|
|
const withNextIntl = createNextIntlPlugin({
|
|
requestConfig: "../../packages/translation/src/request.ts",
|
|
});
|
|
|
|
const nextConfig: NextConfig = {
|
|
// Next previews otherwise create agent instruction files in the application
|
|
// directory during development.
|
|
agentRules: false,
|
|
env: {
|
|
HOMARR_VERSION: process.env.HOMARR_VERSION ?? "unknown",
|
|
},
|
|
output: "standalone",
|
|
reactStrictMode: true,
|
|
// react compiler breaks mantine-react-table, so disabled for now
|
|
//reactCompiler: true,
|
|
/** We already do typechecking as separate tasks in CI */
|
|
typescript: { ignoreBuildErrors: true },
|
|
/**
|
|
* dockerode is required in the external server packages because of https://github.com/homarr-labs/homarr/issues/612
|
|
*
|
|
* Everything else here is a server-only dependency that is cheaper left as a
|
|
* runtime require than inlined into the server bundle. Bundling pulls a package's
|
|
* whole transitive graph into a chunk that is loaded and compiled at boot even
|
|
* when the code path never runs — measurably so for the DB drivers: only
|
|
* better-sqlite3 is ever used, yet inlining mysql2 and pg cost ~21 MiB of
|
|
* resident memory at idle.
|
|
*/
|
|
serverExternalPackages: [
|
|
"dockerode",
|
|
"better-sqlite3",
|
|
"mysql2",
|
|
"pg",
|
|
"winston",
|
|
"drizzle-orm",
|
|
"ical.js",
|
|
"jszip",
|
|
"ldapts",
|
|
"node-unifi",
|
|
"@kubernetes/client-node",
|
|
"linkedom",
|
|
// Only reachable through /api/mcp/[transport].
|
|
"@modelcontextprotocol/sdk",
|
|
],
|
|
experimental: {
|
|
optimizePackageImports: ["@mantine/core", "@mantine/hooks", "@tabler/icons-react"],
|
|
turbopackFileSystemCacheForBuild: true,
|
|
useTypeScriptCli: true,
|
|
},
|
|
turbopack: {
|
|
root: path.resolve(import.meta.dirname, "../.."),
|
|
// Development runs tasks and WebSocket as separate processes. These aliases
|
|
// keep their production-only instrumentation imports out of the dev graph.
|
|
resolveAlias:
|
|
process.env.NODE_ENV === "development"
|
|
? {
|
|
"@homarr/tasks": path.resolve(import.meta.dirname, "src/instrumentation-noop.ts"),
|
|
"@homarr/websocket": path.resolve(import.meta.dirname, "src/instrumentation-noop.ts"),
|
|
}
|
|
: undefined,
|
|
},
|
|
transpilePackages: ["@homarr/ui", "@homarr/notifications", "@homarr/modals", "@homarr/spotlight", "@homarr/widgets"],
|
|
images: {
|
|
localPatterns: [
|
|
{
|
|
pathname: "/**",
|
|
search: "",
|
|
},
|
|
],
|
|
},
|
|
// skipcq: JS-0116
|
|
// eslint-disable-next-line @typescript-eslint/require-await,no-restricted-syntax
|
|
async headers() {
|
|
return [
|
|
{
|
|
source: "/sw.js",
|
|
headers: [
|
|
{ key: "Content-Type", value: "application/javascript; charset=utf-8" },
|
|
{ key: "Cache-Control", value: "no-cache, no-store, must-revalidate" },
|
|
{ key: "Content-Security-Policy", value: "default-src 'self'; script-src 'self'" },
|
|
],
|
|
},
|
|
{
|
|
source: "/(.*)", // Apply CSP to all routes
|
|
headers: [
|
|
{
|
|
key: "Content-Security-Policy",
|
|
// worker-src / media-src with blob: is necessary for video.js, see https://github.com/homarr-labs/homarr/issues/3912 and https://stackoverflow.com/questions/65792855/problem-with-video-js-and-content-security-policy-csp
|
|
value: `
|
|
default-src 'self';
|
|
script-src * 'unsafe-inline' 'unsafe-eval';
|
|
worker-src * blob:;
|
|
base-uri 'self';
|
|
connect-src *;
|
|
style-src * 'unsafe-inline';
|
|
frame-ancestors *;
|
|
frame-src *;
|
|
form-action 'self';
|
|
img-src * data:;
|
|
font-src * data:;
|
|
media-src * data: blob:;
|
|
`
|
|
.replace(/\s{2,}/g, " ")
|
|
.trim(),
|
|
},
|
|
],
|
|
},
|
|
];
|
|
},
|
|
};
|
|
|
|
export default withNextIntl(nextConfig);
|