Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/silly-toes-agree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hyperdx/app": patch
---

fix: next-runtime-env runtime env var injection fixed for images
4 changes: 3 additions & 1 deletion docker/hyperdx/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ COPY --chown=node:node --from=builder /app/packages/app/.next/standalone ./packa
COPY --chown=node:node --from=builder /app/packages/app/.next/static ./packages/app/packages/app/.next/static
COPY --chown=node:node --from=builder /app/packages/app/public ./packages/app/packages/app/public

# Set up start script
# Set up start scripts
COPY --chown=node:node --from=hyperdx ./refresh-env.js /etc/local/refresh-env.js
COPY --chown=node:node --from=hyperdx ./entry.prod.sh /etc/local/entry.sh

HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
Expand Down Expand Up @@ -197,6 +198,7 @@ ENV NODE_ENV=production

# Set up App
COPY --from=prod /app /app
COPY --from=hyperdx ./refresh-env.js /etc/local/refresh-env.js
COPY --from=hyperdx ./entry.local.base.sh /etc/local/entry.base.sh

# Copy VEX exception files for Docker Scout vulnerability suppression
Expand Down
2 changes: 2 additions & 0 deletions docker/hyperdx/entry.local.base.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ echo "ClickHouse is ready!"
# Start Otel Collector with entrypoint script for template rendering and log rotation
/otel-entrypoint.sh /usr/local/bin/opampsupervisor > /var/log/otel-collector.log 2>&1 &

node /etc/local/refresh-env.js

# Start HyperDX app
./node_modules/.bin/concurrently \
"--kill-others-on-fail" \
Expand Down
2 changes: 2 additions & 0 deletions docker/hyperdx/entry.prod.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ echo ""
echo "Visit the HyperDX UI at $FRONTEND_URL"
echo ""

node /etc/local/refresh-env.js

# Optionally include the dashboard provisioner task
EXTRA_NAMES=""
EXTRA_CMDS=""
Expand Down
20 changes: 20 additions & 0 deletions docker/hyperdx/refresh-env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Merge runtime NEXT_PUBLIC_* env vars into the existing __ENV.js written at
// build time by configureRuntimeEnv, so build-time values are preserved and
// runtime values (e.g. docker run -e) take precedence.
const fs = require('fs');

const path = './packages/app/packages/app/public/__ENV.js';

let existing = {};
try {
const content = fs.readFileSync(path, 'utf8');
const json = content.replace(/^window\.__ENV\s*=\s*/, '').replace(/;\s*$/, '');
existing = JSON.parse(json);
} catch {}

const runtime = Object.fromEntries(
Object.entries(process.env).filter(([k]) => k.startsWith('NEXT_PUBLIC_'))
);

const merged = { ...existing, ...runtime };
fs.writeFileSync(path, 'window.__ENV = ' + JSON.stringify(merged) + ';');
33 changes: 20 additions & 13 deletions packages/app/pages/_document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,25 @@ import { Head, Html, Main, NextScript } from 'next/document';

import { IS_CLICKHOUSE_BUILD } from '@/config';
import { ibmPlexMono, inter, roboto, robotoMono } from '@/fonts';
import { themes } from '@/theme';

// Get theme class for SSR - must match ThemeProvider's resolution
// This ensures CSS variables are applied during server-side rendering
// to prevent hydration mismatch with button styling
function getThemeClass(): string {
const envTheme = process.env.NEXT_PUBLIC_THEME;
// Default to hyperdx if not set or invalid
const themeName =
envTheme === 'hyperdx' || envTheme === 'clickstack' ? envTheme : 'hyperdx';
return `theme-${themeName}`;
}
// Applied before React hydrates to prevent a flash of the wrong theme.
// Reads the runtime value from window.__ENV (set by __ENV.js) and swaps
// the theme class on <html> so CSS variables are correct on first paint.
const validThemes = Object.keys(themes);
const themeClasses = validThemes.map(t => `theme-${t}`);
const THEME_INIT_SCRIPT = `
(function () {
var theme = window.__ENV && window.__ENV.NEXT_PUBLIC_THEME;
var valid = ${JSON.stringify(validThemes)};
if (valid.indexOf(theme) !== -1) {
var html = document.documentElement;
var remove = ${JSON.stringify(themeClasses)};
for (var i = 0; i < remove.length; i++) html.classList.remove(remove[i]);
html.classList.add('theme-' + theme);
}
})();
`;

export default function Document() {
const fontClasses = [
Expand All @@ -22,13 +30,12 @@ export default function Document() {
roboto.variable,
].join(' ');

const themeClass = getThemeClass();

return (
<Html lang="en" className={`${fontClasses} ${themeClass}`}>
<Html lang="en" className={`${fontClasses} theme-hyperdx`}>
<Head>
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
<script src="/__ENV.js" />
<script dangerouslySetInnerHTML={{ __html: THEME_INIT_SCRIPT }} />
{!IS_CLICKHOUSE_BUILD && (
<>
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
Expand Down
13 changes: 9 additions & 4 deletions packages/app/src/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { env } from 'next-runtime-env';
import { z } from 'zod';

import { clickstackTheme } from './themes/clickstack';
Expand Down Expand Up @@ -171,10 +172,14 @@ export function safeLocalStorageRemove(key: string): void {
}

// Default theme (validated against registry, falls back to hyperdx)
const envTheme = process.env.NEXT_PUBLIC_THEME;
let resolvedDefaultTheme: ThemeName = isValidThemeName(envTheme)
? envTheme
: 'hyperdx';
// Use runtimeEnv so the theme can be changed at container start-up
// without rebuilding the Next.js bundle.
function getEnvTheme(): ThemeName {
const value = env('NEXT_PUBLIC_THEME');
return isValidThemeName(value) ? value : 'hyperdx';
}

let resolvedDefaultTheme: ThemeName = getEnvTheme();

// Validate that the resolved default theme exists and is valid
if (!themes[resolvedDefaultTheme]) {
Expand Down
Loading