P1-0: V8 heap cap (HOMARR_MAX_OLD_SPACE_SIZE, default 512MB)
P1-1: Module graph cuts
- Dynamic integration imports (55 static → lazy loaders)
- Conditional DB driver loading (only configured driver loaded)
- Replace jsdom with linkedom for SVG sanitization
- Delete eager widget barrel (55 import * as → 0)
- Extract createRetryableLoader to @homarr/common
P1-2: Enable cacheComponents + restructure root layout
- Suspense boundaries around session-dependent providers
- ColorSchemeScript replaces server-side cookie reads
- Board/manage/auth layouts wrapped in Suspense
P1-3: Cache read paths
- Cache tag registry + invalidation helpers
- Cache invalidation wired into all mutation routers
- Translation deep-merge memoized per locale
P1-4: Prerendered shells + loading.tsx files
P1-5: Shared integration data cache (60s TTL, stale-while-revalidate)
P1-6: Redis-backed Next.js cache handler (@homarr/next-cache-handler)
P1-7: Redis optional (memory channel fallback), TTL on pubSub:last:*,
Redis clients consolidated from 6 to 2
118 lines
3.9 KiB
TypeScript
118 lines
3.9 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 getDevelopmentServiceAliases = () => {
|
|
if (process.env.NODE_ENV !== "development") {
|
|
return undefined;
|
|
}
|
|
|
|
return {
|
|
"@homarr/tasks": "./src/instrumentation-noop.ts",
|
|
"@homarr/websocket": "./src/instrumentation-noop.ts",
|
|
};
|
|
};
|
|
|
|
const nextConfig: NextConfig = {
|
|
cacheHandlers: {
|
|
default: path.resolve(import.meta.dirname, "../../packages/next-cache-handler/src/index.ts"),
|
|
},
|
|
// 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,
|
|
cacheComponents: true,
|
|
cacheMaxMemorySize: 32 * 1024 * 1024,
|
|
cacheLife: {
|
|
config: { stale: 60, revalidate: 60 * 60 * 24, expire: 60 * 60 * 24 * 7 },
|
|
integration: { stale: 15, revalidate: 60, expire: 60 * 10 },
|
|
session: { stale: 30, revalidate: 60 * 5, expire: 60 * 30 },
|
|
},
|
|
// 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
|
|
*/
|
|
serverExternalPackages: ["dockerode", "better-sqlite3", "mysql2", "pg"],
|
|
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: getDevelopmentServiceAliases(),
|
|
},
|
|
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);
|