diff --git a/.changeset/solid-pigs-rhyme.md b/.changeset/solid-pigs-rhyme.md new file mode 100644 index 0000000000..344bcb74b8 --- /dev/null +++ b/.changeset/solid-pigs-rhyme.md @@ -0,0 +1,6 @@ +--- +'@hyperdx/app': patch +--- + +Transition the local development server from Webpack to Turbopack to +significantly improve build performance and hot-reloading speed. diff --git a/packages/app/next.config.mjs b/packages/app/next.config.mjs index fa2ee03efb..579a5fc4e2 100644 --- a/packages/app/next.config.mjs +++ b/packages/app/next.config.mjs @@ -31,22 +31,41 @@ const nextConfig = { }, // External packages to prevent bundling issues (moved from experimental in Next.js 15+) // https://github.com/open-telemetry/opentelemetry-js/issues/4297#issuecomment-2285070503 + // + // `serverExternalPackages` is bundler-agnostic: it applies to both Webpack + // (production `build`) and Turbopack (local `dev`), so the server-bundle + // externalization below stays consistent across both bundlers. serverExternalPackages: [ '@opentelemetry/instrumentation', '@opentelemetry/sdk-node', '@opentelemetry/auto-instrumentations-node', '@hyperdx/node-opentelemetry', '@hyperdx/instrumentation-sentry-node', + // Outside of Vercel preview deployments, the `/api/[...all]` catch-all + // proxies to a separately-deployed API service and never imports the + // `@hyperdx/api` package at runtime. Keep it (and its subpaths) external + // so server bundles do not pull in passport-saml, mongoose, AWS SDK, etc. + // This must stay external under both Webpack and Turbopack; otherwise the + // Turbopack dev server would attempt to bundle the entire `@hyperdx/api` + // dependency tree (slow startup / module-resolution errors). + ...(process.env.HDX_PREVIEW_INLINE_API !== 'true' ? ['@hyperdx/api'] : []), ], typescript: { tsconfigPath: 'tsconfig.build.json', }, - // NOTE: Using Webpack instead of Turbopack (Next.js 16 default) - // Reason: Turbopack has CSS module parsing issues with nested :global syntax - // used in styles/SearchPage.module.scss and other SCSS files. - // The --webpack flag is added to dev and build scripts in package.json. - // TODO: Re-evaluate when Turbopack CSS module support improves - // Ignore otel pkgs warnings + // NOTE: `dev`/`dev:local` run on Turbopack (Next.js 16 default) while + // `build`/`build:clickhouse` run on Webpack (the `--webpack` flag in + // package.json). The split exists because Turbopack historically had CSS + // module parsing issues with nested :global syntax used in + // styles/SearchPage.module.scss and other SCSS files; dev tolerates this + // while production builds stay on the known-good Webpack path. Because the + // two bundlers can diverge (CSS modules, loaders, module resolution), prefer + // bundler-agnostic config (e.g. `serverExternalPackages` above) over the + // Webpack-only `webpack` callback below, which Turbopack silently ignores. + // TODO: Collapse onto a single bundler once Turbopack CSS module support and + // build parity are confirmed. + // + // Ignore otel pkgs warnings (Webpack-only; harmless to skip under Turbopack) // https://github.com/open-telemetry/opentelemetry-js/issues/4173#issuecomment-1822938936 webpack: ( config, @@ -54,27 +73,6 @@ const nextConfig = { ) => { if (isServer) { config.ignoreWarnings = [{ module: /opentelemetry/ }]; - - // Outside of Vercel preview deployments, the `/api/[...all]` catch-all - // proxies to a separately-deployed API service and never imports the - // `@hyperdx/api` package at runtime. Mark it (and its subpaths) as a - // CommonJS external so production app builds (Docker fullstack image, - // standalone Next output) stay byte-for-byte equivalent to today and - // do not pull in passport-saml, mongoose, AWS SDK, etc. - if (process.env.HDX_PREVIEW_INLINE_API !== 'true') { - config.externals = [ - ...(config.externals ?? []), - ({ request }, callback) => { - if ( - request === '@hyperdx/api' || - request?.startsWith?.('@hyperdx/api/') - ) { - return callback(null, `commonjs ${request}`); - } - return callback(); - }, - ]; - } } return config; }, diff --git a/packages/app/package.json b/packages/app/package.json index 931a238491..208a6012c2 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -6,9 +6,10 @@ "engines": { "node": ">=22.16.0" }, + "//bundler": "dev/dev:local run on Turbopack for fast local iteration; build/build:clickhouse run on Webpack (--webpack) for production parity. The two bundlers can diverge on CSS Modules/loaders/module resolution, so build failures may not reproduce in dev. See next.config.mjs for rationale.", "scripts": { - "dev": "npx dotenv -e .env.development -- next dev --webpack", - "dev:local": "NEXT_PUBLIC_IS_LOCAL_MODE=true npx dotenv -e .env.development -- next dev --webpack", + "dev": "npx dotenv -e .env.development -- next dev --turbopack", + "dev:local": "NEXT_PUBLIC_IS_LOCAL_MODE=true npx dotenv -e .env.development -- next dev --turbopack", "build": "next build --webpack", "build:clickhouse": "NEXT_PUBLIC_THEME=clickstack NEXT_PUBLIC_IS_LOCAL_MODE=true NEXT_PUBLIC_CLICKHOUSE_BUILD=true next build --webpack && node scripts/prepare-clickhouse-build-export.js", "run:clickhouse": "test -d out && npx rimraf tmp && mkdir tmp && cp -r out tmp/clickstack && echo 'visit http://localhost:3000/clickstack to start' && npx serve tmp -l 3000 || echo 'run build:clickhouse first'", diff --git a/packages/app/styles/SearchPage.module.scss b/packages/app/styles/SearchPage.module.scss index 3e74bd07e0..c617de5d10 100644 --- a/packages/app/styles/SearchPage.module.scss +++ b/packages/app/styles/SearchPage.module.scss @@ -14,28 +14,26 @@ overflow: hidden; z-index: 3; // higher z-index to be above other elements - :global { - .mantine-TextInput-wrapper { - background-color: transparent; - } + :global(.mantine-TextInput-wrapper) { + background-color: transparent; + } - .mantine-TextInput-input { - height: 20px; - min-height: 20px; - background-color: transparent; - color: var(--color-text-secondary); - font-size: var(--mantine-font-size-xs); + :global(.mantine-TextInput-input) { + height: 20px; + min-height: 20px; + background-color: transparent; + color: var(--color-text-secondary); + font-size: var(--mantine-font-size-xs); + } - &::placeholder { - color: var(--color-text-muted); - font-weight: bold; - } + :global(.mantine-TextInput-input::placeholder) { + color: var(--color-text-muted); + font-weight: bold; + } - &:focus { - border: none; - outline: none; - } - } + :global(.mantine-TextInput-input:focus) { + border: none; + outline: none; } }