From ef5adacdf44e877ff45331a1ff6882d3eca65263 Mon Sep 17 00:00:00 2001 From: datnguyennnx Date: Fri, 3 Oct 2025 16:40:32 +0700 Subject: [PATCH 1/4] feat: add subpath config --- .env | 1 + docker-compose.dev.yml | 23 +++++++++++++++++ packages/api/.env.development | 2 +- packages/app/next.config.js | 5 ++-- proxy/nginx/nginx.conf.template | 46 +++++++++++++++++++++++++++++++++ proxy/traefik/config.yml | 35 +++++++++++++++++++++++++ proxy/traefik/traefik.yml | 8 ++++++ 7 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 proxy/nginx/nginx.conf.template create mode 100644 proxy/traefik/config.yml create mode 100644 proxy/traefik/traefik.yml diff --git a/.env b/.env index 685e5cb85..e8c738b3f 100644 --- a/.env +++ b/.env @@ -20,6 +20,7 @@ HYPERDX_APP_PORT=8080 HYPERDX_APP_URL=http://localhost HYPERDX_LOG_LEVEL=debug HYPERDX_OPAMP_PORT=4320 +HYPERDX_BASE_PATH=/hyperdx # Otel/Clickhouse config HYPERDX_OTEL_EXPORTER_CLICKHOUSE_DATABASE=default diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 7cf0381a5..639ae1a51 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -83,5 +83,28 @@ services: interval: 1s timeout: 1s retries: 60 + + # nginx: + # image: nginx:alpine + # ports: + # - '4040:4040' + # volumes: + # - ./proxy/nginx/nginx.conf.template:/etc/nginx/templates/default.conf.template:ro + # environment: + # HYPERDX_BASE_PATH: ${HYPERDX_BASE_PATH} + # network_mode: host + # restart: always + + # traefik: + # image: traefik:latest + # ports: + # - '4040:4040' + # volumes: + # - ./proxy/traefik/traefik.yml:/etc/traefik/traefik.yml:ro + # - ./proxy/traefik/config.yml:/etc/traefik/dynamic/config.yml:ro + # environment: + # HYPERDX_BASE_PATH: ${HYPERDX_BASE_PATH} + # network_mode: host + # restart: always networks: internal: diff --git a/packages/api/.env.development b/packages/api/.env.development index 344ec2c3d..e55dd2ed2 100644 --- a/packages/api/.env.development +++ b/packages/api/.env.development @@ -4,7 +4,7 @@ HYPERDX_OPAMP_PORT=4320 HYPERDX_APP_PORT=8080 HYPERDX_LOG_LEVEL=debug EXPRESS_SESSION_SECRET="hyperdx is cool 👋" -FRONTEND_URL="http://localhost:${HYPERDX_APP_PORT}" +FRONTEND_URL="http://localhost:4040/hyperdx" HDX_NODE_ADVANCED_NETWORK_CAPTURE=1 HDX_NODE_BETA_MODE=1 HDX_NODE_CONSOLE_CAPTURE=1 diff --git a/packages/app/next.config.js b/packages/app/next.config.js index f4b04a4d5..e43267e26 100644 --- a/packages/app/next.config.js +++ b/packages/app/next.config.js @@ -9,6 +9,7 @@ const withNextra = require('nextra')({ }); module.exports = { + basePath: process.env.HYPERDX_BASE_PATH || '', experimental: { instrumentationHook: true, // External packages to prevent bundling issues with Next.js 14 @@ -57,8 +58,8 @@ module.exports = { productionBrowserSourceMaps: false, ...(process.env.NEXT_OUTPUT_STANDALONE === 'true' ? { - output: 'standalone', - } + output: 'standalone', + } : {}), }), }; diff --git a/proxy/nginx/nginx.conf.template b/proxy/nginx/nginx.conf.template new file mode 100644 index 000000000..8cd6c7ade --- /dev/null +++ b/proxy/nginx/nginx.conf.template @@ -0,0 +1,46 @@ +upstream app { + server 127.0.0.1:8080; +} + +server { + listen 4040; + + # Proxy _next assets to the app with base path prefix + location /_next { + proxy_pass http://app${HYPERDX_BASE_PATH}/_next; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Proxy /api to base path/api + location /api { + proxy_pass http://app${HYPERDX_BASE_PATH}/api; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Proxy base path to the app + location ${HYPERDX_BASE_PATH} { + proxy_pass http://app${HYPERDX_BASE_PATH}; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/proxy/traefik/config.yml b/proxy/traefik/config.yml new file mode 100644 index 000000000..39e4f5196 --- /dev/null +++ b/proxy/traefik/config.yml @@ -0,0 +1,35 @@ +http: + routers: + next-router: + entryPoints: + - web + rule: 'PathPrefix(`/_next`)' + service: app-service + middlewares: + - add-basepath + + api-router: + entryPoints: + - web + rule: 'PathPrefix(`/api`)' + service: app-service + middlewares: + - add-basepath + + basepath-router: + entryPoints: + - web + rule: 'PathPrefix(`{{ env "HYPERDX_BASE_PATH" }}`)' + service: app-service + + middlewares: + add-basepath: + addPrefix: + prefix: '{{ env "HYPERDX_BASE_PATH" }}' + + services: + app-service: + loadBalancer: + passHostHeader: true + servers: + - url: 'http://127.0.0.1:8080' diff --git a/proxy/traefik/traefik.yml b/proxy/traefik/traefik.yml new file mode 100644 index 000000000..7d3ff7471 --- /dev/null +++ b/proxy/traefik/traefik.yml @@ -0,0 +1,8 @@ +entryPoints: + web: + address: ':4040' + +providers: + file: + filename: /etc/traefik/dynamic/config.yml + watch: true From b52b467f71d361b7b7ab1b82ee50c518bb4817ed Mon Sep 17 00:00:00 2001 From: datnguyennnx Date: Thu, 9 Oct 2025 23:03:48 +0700 Subject: [PATCH 2/4] fix: naming and config proxy --- .env | 2 +- docker-compose.dev.yml | 5 ++- packages/app/.env.development | 1 + packages/app/next.config.js | 4 +- proxy/README.md | 74 +++++++++++++++++++++++++++++++++ proxy/nginx/nginx.conf.template | 67 ++++++++++++++--------------- proxy/traefik/config.yml | 29 +++++++++---- 7 files changed, 134 insertions(+), 48 deletions(-) create mode 100644 proxy/README.md diff --git a/.env b/.env index e8c738b3f..c8f25be9e 100644 --- a/.env +++ b/.env @@ -20,7 +20,7 @@ HYPERDX_APP_PORT=8080 HYPERDX_APP_URL=http://localhost HYPERDX_LOG_LEVEL=debug HYPERDX_OPAMP_PORT=4320 -HYPERDX_BASE_PATH=/hyperdx +HYPERDX_BASE_PATH= # Otel/Clickhouse config HYPERDX_OTEL_EXPORTER_CLICKHOUSE_DATABASE=default diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 639ae1a51..d376d4a24 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -91,7 +91,7 @@ services: # volumes: # - ./proxy/nginx/nginx.conf.template:/etc/nginx/templates/default.conf.template:ro # environment: - # HYPERDX_BASE_PATH: ${HYPERDX_BASE_PATH} + # HYPERDX_BASE_PATH: ${HYPERDX_BASE_PATH:-/} # network_mode: host # restart: always @@ -103,8 +103,9 @@ services: # - ./proxy/traefik/traefik.yml:/etc/traefik/traefik.yml:ro # - ./proxy/traefik/config.yml:/etc/traefik/dynamic/config.yml:ro # environment: - # HYPERDX_BASE_PATH: ${HYPERDX_BASE_PATH} + # HYPERDX_BASE_PATH: ${HYPERDX_BASE_PATH:-/} # network_mode: host # restart: always + networks: internal: diff --git a/packages/app/.env.development b/packages/app/.env.development index 15c5e343e..0c63da39c 100644 --- a/packages/app/.env.development +++ b/packages/app/.env.development @@ -7,3 +7,4 @@ OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318" OTEL_SERVICE_NAME="hdx-oss-dev-app" PORT=${HYPERDX_APP_PORT} NODE_OPTIONS="--max-http-header-size=131072" +NEXT_PUBLIC_HYPERDX_BASE_PATH= \ No newline at end of file diff --git a/packages/app/next.config.js b/packages/app/next.config.js index e43267e26..ee8a943a8 100644 --- a/packages/app/next.config.js +++ b/packages/app/next.config.js @@ -8,8 +8,10 @@ const withNextra = require('nextra')({ themeConfig: './src/nextra.config.tsx', }); +const basePath = process.env.NEXT_PUBLIC_HYPERDX_BASE_PATH; + module.exports = { - basePath: process.env.HYPERDX_BASE_PATH || '', + basePath: basePath, experimental: { instrumentationHook: true, // External packages to prevent bundling issues with Next.js 14 diff --git a/proxy/README.md b/proxy/README.md new file mode 100644 index 000000000..04874530e --- /dev/null +++ b/proxy/README.md @@ -0,0 +1,74 @@ +# Proxy Configuration + +This directory contains configurations for running the HyperDX frontend behind a +reverse proxy that serves the application under a specific subpath. This is +useful for deployments where HyperDX is not at the root of a domain (e.g., +`http://example.com/hyperdx`). + +We provide configurations for two popular reverse proxies: + +- [Nginx](./nginx/nginx.conf.template) +- [Traefik](./traefik/config.yml) + +## Environment Variables + +To configure the subpath, you need to set the following environment variables in +your `.env` file. + +### `HYPERDX_BASE_PATH` and `NEXT_PUBLIC_HYPERDX_BASE_PATH` + +To serve the application from a subpath, two environment variables must be set +to the **same value**: + +1. `HYPERDX_BASE_PATH`: This is used by the reverse proxy (Nginx or Traefik) to + handle path routing and rewriting. +2. `NEXT_PUBLIC_HYPERDX_BASE_PATH`: This is used by the Next.js application to + generate correct asset links and API routes. + +- The value **must** start with a `/` if it's not an empty string (ex: + `/hyperdx`). +- If you want to serve from the root, you can omit these variables or set them + to `/`. + +### `FRONTEND_URL` + +This variable should be set to the full public URL of the frontend, including +the subpath. The API server uses this URL for various purposes such as +generating absolute URLs for redirects, links in emails, or alerts. + +- It should be a full URL, including the protocol (`http` or `https`). +- It should include the subpath defined in `HYPERDX_BASE_PATH`. + +**Example `.env` Configuration:** + +For local development with the subpath `/hyperdx`, your configuration would look +like this: + +``` +HYPERDX_BASE_PATH=/hyperdx +NEXT_PUBLIC_HYPERDX_BASE_PATH=/hyperdx +FRONTEND_URL=http://localhost:4040/hyperdx +``` + +## How It Works + +The proxy configurations are designed to handle subpath routing with minimal +changes to the application code. Here's a high-level overview of the logic: + +1. **Root Redirect**: If a subpath is configured (e.g., `/hyperdx`), any + requests to the root (`/`) are automatically redirected to that subpath. + This ensures users always land on the correct URL. + +2. **Path Rewriting**: The application's frontend code sometimes makes requests + to root-level paths (e.g., `/api/...` or `/_next/...`). The proxy intercepts + these requests, prepends the configured subpath, and forwards them to the + Next.js server. For example, a request for `/_next/static/chunk.js` becomes + a request for `/hyperdx/_next/static/chunk.js` before being sent to the + application. + +3. **Direct Proxy**: Any requests that already include the correct subpath are + passed directly to the Next.js application, which is configured via + `basePath` to handle them correctly. + +This setup allows the frontend application to be developed as if it were running +at the root, while the proxy transparently manages the subpath routing. diff --git a/proxy/nginx/nginx.conf.template b/proxy/nginx/nginx.conf.template index 8cd6c7ade..34749b35f 100644 --- a/proxy/nginx/nginx.conf.template +++ b/proxy/nginx/nginx.conf.template @@ -4,43 +4,40 @@ upstream app { server { listen 4040; - - # Proxy _next assets to the app with base path prefix - location /_next { - proxy_pass http://app${HYPERDX_BASE_PATH}/_next; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + + set $base_path "${HYPERDX_BASE_PATH}"; + if ($base_path = "/") { + set $base_path ""; + } + + # Common proxy headers + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Redirect root to base path, if a base path is set + location = / { + if ($base_path != "") { + return 301 $base_path; + } + # If no base path, just proxy to the app + proxy_pass http://app; } - - # Proxy /api to base path/api - location /api { - proxy_pass http://app${HYPERDX_BASE_PATH}/api; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + + # This handles assets and api calls made to the root and rewrites them to include the base path + location ~ ^(/api/|/_next/|/__ENV\.js$|/Icon32\.png$) { + # Note: $request_uri includes the original full path including query string + proxy_pass http://app$base_path$request_uri; } - - # Proxy base path to the app + + # Proxy requests that are already prefixed with the base path to the app location ${HYPERDX_BASE_PATH} { - proxy_pass http://app${HYPERDX_BASE_PATH}; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + # The full request URI (e.g., /hyperdx/settings) is passed to the upstream + proxy_pass http://app; } } \ No newline at end of file diff --git a/proxy/traefik/config.yml b/proxy/traefik/config.yml index 39e4f5196..bc7625941 100644 --- a/proxy/traefik/config.yml +++ b/proxy/traefik/config.yml @@ -1,32 +1,43 @@ http: routers: - next-router: + # This handles the main app at the basepath + app-router: entryPoints: - web - rule: 'PathPrefix(`/_next`)' + rule: 'PathPrefix(`{{ env "HYPERDX_BASE_PATH" }}`)' service: app-service - middlewares: - - add-basepath - api-router: + # This handles assets and api calls at the root and rewrites them + assets-api-router: entryPoints: - web - rule: 'PathPrefix(`/api`)' + rule: + 'PathPrefix(`/api`) || PathPrefix(`/_next`) || Path(`/__ENV.js`) || + Path(`/Icon32.png`)' service: app-service middlewares: - add-basepath - basepath-router: + # This redirects from / to the basepath + root-redirect: entryPoints: - web - rule: 'PathPrefix(`{{ env "HYPERDX_BASE_PATH" }}`)' - service: app-service + rule: 'Path(`/`)' + service: app-service # service is required, but redirect will happen first + middlewares: + - redirect-to-basepath middlewares: add-basepath: addPrefix: prefix: '{{ env "HYPERDX_BASE_PATH" }}' + redirect-to-basepath: + redirectRegex: + regex: '^/$' + replacement: '{{ env "HYPERDX_BASE_PATH" }}' + permanent: true + services: app-service: loadBalancer: From 2e1037ebd4cdc790d99e87e64596c86ea1028ba0 Mon Sep 17 00:00:00 2001 From: datnguyennnx Date: Thu, 9 Oct 2025 23:05:55 +0700 Subject: [PATCH 3/4] chore: restore frontend url --- packages/api/.env.development | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/.env.development b/packages/api/.env.development index e55dd2ed2..344ec2c3d 100644 --- a/packages/api/.env.development +++ b/packages/api/.env.development @@ -4,7 +4,7 @@ HYPERDX_OPAMP_PORT=4320 HYPERDX_APP_PORT=8080 HYPERDX_LOG_LEVEL=debug EXPRESS_SESSION_SECRET="hyperdx is cool 👋" -FRONTEND_URL="http://localhost:4040/hyperdx" +FRONTEND_URL="http://localhost:${HYPERDX_APP_PORT}" HDX_NODE_ADVANCED_NETWORK_CAPTURE=1 HDX_NODE_BETA_MODE=1 HDX_NODE_CONSOLE_CAPTURE=1 From 81847d3c29d76a17ef2e31baa387c2f065b5fb1f Mon Sep 17 00:00:00 2001 From: datnguyennnx Date: Thu, 9 Oct 2025 23:18:21 +0700 Subject: [PATCH 4/4] chore: add changeset --- .changeset/feat-subpath-config.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/feat-subpath-config.md diff --git a/.changeset/feat-subpath-config.md b/.changeset/feat-subpath-config.md new file mode 100644 index 000000000..18d179c2b --- /dev/null +++ b/.changeset/feat-subpath-config.md @@ -0,0 +1,9 @@ +--- +'@hyperdx/app': minor +--- + +feat: Add subpath configuration support + +This change allows the HyperDX frontend to be served from a subpath (e.g., +`/hyperdx`). It includes updated Next.js, NGINX, and Traefik configurations, +along with documentation for the new setup.