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..ef3a3fc3ee 100644 --- a/packages/app/next.config.mjs +++ b/packages/app/next.config.mjs @@ -37,17 +37,22 @@ const nextConfig = { '@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. 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. + ...(process.env.HDX_PREVIEW_INLINE_API !== 'true' ? ['@hyperdx/api'] : []), ], typescript: { tsconfigPath: 'tsconfig.build.json', }, - // NOTE: Using Webpack instead of Turbopack (Next.js 16 default) + // Dev uses Turbopack; production build uses Webpack (--webpack). // 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 - // https://github.com/open-telemetry/opentelemetry-js/issues/4173#issuecomment-1822938936 + // TODO: single bundler when Turbopack CSS is solid. + // Ignore otel warnings (Webpack): https://github.com/open-telemetry/opentelemetry-js/issues/4173#issuecomment-1822938936 webpack: ( config, { buildId, dev, isServer, defaultLoaders, nextRuntime, webpack }, @@ -55,12 +60,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 ?? []), diff --git a/packages/app/package.json b/packages/app/package.json index 86edb4b74e..5347f61d44 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/pages/api/[...all].ts b/packages/app/pages/api/[...all].ts index bfc3bd590a..5b6361d0b2 100644 --- a/packages/app/pages/api/[...all].ts +++ b/packages/app/pages/api/[...all].ts @@ -16,13 +16,13 @@ export const config = { // we proxy `/api/*` to a separately-deployed API service as before. const isInlineApi = process.env.HDX_PREVIEW_INLINE_API === 'true'; -export default (req: NextApiRequest, res: NextApiResponse) => { +export default async (req: NextApiRequest, res: NextApiResponse) => { if (isInlineApi) { - // Lazy require so non-preview production builds — where the webpack - // externals hook in next.config.mjs marks @hyperdx/api as external — - // never attempt to resolve a module that isn't bundled. - // eslint-disable-next-line @typescript-eslint/no-require-imports - const inlineApi = require('@hyperdx/api/build/serverless'); + const inlineApi = await import( + /* webpackIgnore: true */ + /* turbopackIgnore: true */ + '@hyperdx/api/build/serverless' + ); const handler = inlineApi.default ?? inlineApi; return handler(req, res); } 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; } }