Two further wins, both measured on a restored 28-widget board behind a real demo session (scripts/benchmarks/stress-restore.mts). --max-semi-space-size=4 in run.sh. --max-old-space-size only bounds the old generation; V8 also sizes the young generation generously (16 MB per semi-space) and that slack is pure resident memory. 4 MB cut idle anonymous memory 120.0 -> 97.2 MiB and peak 710.6 -> 560.2 MiB. It costs CPU, as any GC-space reduction does — 15.8 -> 17.1 CPU-seconds over the run, ~8% — with no board-load latency regression (median 594 -> 570 ms). Overridable via HOMARR_MAX_SEMI_SPACE_SIZE. serverExternalPackages for the remaining server-only dependencies. Bundling a package inlines its transitive graph into a chunk that is compiled at boot even when the code path never runs; leaving it external makes it a lazy require. Adding winston, drizzle-orm, ical.js, jszip, ldapts, node-unifi, @kubernetes/client-node and linkedom took idle to 90.3 MiB and board-loaded 209.6 -> 164.6 MiB, with slightly lower CPU (16.3s) and latency (549 ms). The trade-off is image size: 388 -> 401 MB, because those packages now ship in node_modules instead of being tree-shaken into chunks. Cumulative vs the merge-base: idle 141.7 -> 90.3 MiB (-36%), board loaded 257.8 -> 164.6 MiB (-36%), under load 392.7 -> 302.5 MiB (-23%). The harness now proves the session rather than inferring it: it asserts an anonymous request to the private board is denied, requires the Auth.js session cookie after sign-in, and reports whether it had to modify the restored data at all (now "none"). It also records cgroup CPU seconds and board-load latency, so a memory win bought with CPU cannot hide. pnpm test 144/144 files, 1795 tests; typecheck and lint clean.
123 lines
4.1 KiB
TypeScript
123 lines
4.1 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",
|
|
],
|
|
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);
|