Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions frontend/netlify.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[build]
base = "frontend"
# NODE_OPTIONS: 16 GB is a V8 heap ceiling guard, not actual memory usage.
# It prevents OOM kills during large builds; real RSS stays well below this.
command = "bun install && NODE_OPTIONS=--max_old_space_size=16384 bun run build"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we charged by memory consumption in builds? having a high node old space size could be hiding some bugs.

Copy link
Copy Markdown
Contributor Author

@malinskibeniamin malinskibeniamin Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. The --max_old_space_size=16384 is a V8 heap ceiling, not actual memory allocation — it just raises the limit before V8 triggers an OOM. The actual RSS during a Console build is typically 2-4 GB.

Netlify doesn't charge by memory consumption — builds run on shared VMs with a fixed time limit. The risk of a high ceiling is exactly what you said: it could mask a memory leak in the bundler that would otherwise surface as an OOM.

That said, other app's Netlify build uses NODE_OPTIONS=--max_old_space_size=4096 (see netlify.toml). We could lower this to 4096 or 8192 to be more conservative while still avoiding legitimate OOM during large builds.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lowered to 4096 in 286d804 to match Cloud UI's pattern.

publish = "build"

[build.environment]
NODE_VERSION = "22"
BUN_VERSION = "1.3.11"

[[headers]]
for = "/*"
[headers.values]
X-Content-Type-Options = "nosniff"

# MF remote entry — never cached, CORS required for cross-origin import
[[headers]]
for = "/embedded.js"
[headers.values]
Cache-Control = "no-cache, no-store, must-revalidate"
Access-Control-Allow-Origin = "*"
Access-Control-Allow-Methods = "GET, OPTIONS"
Access-Control-Allow-Headers = "*"
Access-Control-Max-Age = "86400"

# MF manifest — never cached, CORS required for cross-origin fetch
[[headers]]
for = "/mf-manifest.json"
[headers.values]
Cache-Control = "no-cache, no-store, must-revalidate"
Access-Control-Allow-Origin = "*"
Access-Control-Allow-Methods = "GET, OPTIONS"
Access-Control-Allow-Headers = "*"
Access-Control-Max-Age = "86400"

# Hashed static assets are immutable, CORS required for cross-origin import
[[headers]]
for = "/static/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
Access-Control-Allow-Origin = "*"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this how we manage it normally, could we enable CORS only for the specific domains ?

Copy link
Copy Markdown
Contributor Author

@malinskibeniamin malinskibeniamin Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. For Module Federation, the host app (Cloud UI on cloud.redpanda.com) loads remoteEntry.js and chunk files from this Netlify site via <script> tags and fetch(). Using Access-Control-Allow-Origin: * is standard for public static assets (same pattern as any CDN-served JS library).

We could restrict to specific domains (e.g., *.redpanda.com, *.netlify.app), but:

  • These are public, non-sensitive static JS/CSS files with no auth
  • Host app Netlify site uses the same Access-Control-Allow-Origin: * pattern for external scripts

The wildcard is scoped to only the MF entry points (/embedded.js, /mf-manifest.json, /static/*), not all responses. The catch-all /* block only has X-Content-Type-Options: nosniff without CORS. So the exposure is limited to files that are designed to be publicly fetchable.

Access-Control-Allow-Methods = "GET, OPTIONS"
Access-Control-Allow-Headers = "*"
Access-Control-Max-Age = "86400"
3 changes: 3 additions & 0 deletions frontend/public/_redirects
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPA fallback for standalone mode. In embedded (MF) mode, only
# remoteEntry.js/embedded.js and chunks are fetched.
/* /index.html 200
8 changes: 7 additions & 1 deletion frontend/src/federation/console-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,13 @@ function ConsoleAppInner({
if (navigateTo !== currentPath) {
// Update ref to prevent echo back to host
lastNotifiedPathRef.current = navigateTo;
router.navigate({ to: navigateTo });
const qIdx = navigateTo.indexOf('?');
const toPath = qIdx >= 0 ? navigateTo.slice(0, qIdx) : navigateTo;
const toSearch = qIdx >= 0 ? navigateTo.slice(qIdx + 1) : undefined;
router.navigate({
to: toPath,
search: toSearch ? Object.fromEntries(new URLSearchParams(toSearch)) : undefined,
});
}
}, [navigateTo, isInitialized, router]);

Expand Down
Loading