Repository navigation
perf(dashboard): Drop zod locales and Drizzle from the browser bundle - #2731
Merged
Merged
Conversation
Import zod as a namespace everywhere. esbuild cannot tree-shake the named z export, so it kept all zod locales (~250 KB) in the dashboard entry. An ast-grep rule keeps the named import out. Move the agent invocation statuses and the destination kind schema out of the Drizzle table modules, so @sentry/junior/api/schema no longer pulls drizzle-orm into the browser. Resolve @sentry/junior-plugin-api to one copy in the client build, so the browser loads it once.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
|
2 screenshot changes — 2 changed · 0 added · 0 removed Review screenshots in Frameshift
|
BYK
requested changes
Nov 5, 2026
BYK
left a comment
Member
There was a problem hiding this comment.
Always prefer oxlint rules over ast-grep when possible and makes sense.
BYK
approved these changes
Nov 5, 2026
BYK
pushed a commit
that referenced
this pull request
Nov 5, 2026
…and (#2734) Follow-up to #2731. The browser downloaded every dashboard page, recharts with its d3/redux deps, and Shiki's core before it could paint the conversation workspace. Now each of those loads when it's first needed. First load = the entry plus every chunk it imports statically (esbuild metafile of `tsup.client.config.ts`): | first load | files | raw | gzip-9 | br-11 | |---|---|---|---|---| | main | 2 | 2,448,020 | 741,377 | 606,927 | | this PR | 55 | 1,712,998 | 550,926 | 478,718 | | change | | −735 KB (−30%) | −190 KB (−26%) | −128 KB (−21%) | Recharts, d3, redux, immer, the Shiki core and TextMate engine, and all `src/client/pages` code except the layouts and loading skeletons are no longer on the first load. ## Changes - **Lazy pages:** in `App.tsx`, every route page outside the conversation workspace is now `React.lazy`: automations, code, locations, people, settings, system, workspaces, plugin and memory pages, and admin. The conversation workspace and review desk stay eager. Layouts and loading skeletons stay eager, so route-loading states still show at once. `TracedRoutes` takes a `fallback` and wraps the matched route in `Suspense`. - **`pluginUserPagePath` moved** to `pages/user/pluginUserPageRoutes.ts`. `ProfileMenu` and `primaryNav` imported the helper from `PluginUserPage.tsx`, and that pulled the memory page and recharts into the entry. This uses the same pattern as `codeRepositoryRoutes.ts`. - **Shiki loads with the first code block.** The highlighter moved to `code-highlighter.ts`, and `HighlightedCode` imports it inside its existing async query. Before the first highlight it shows the same plain `HighlightedCodeFallback` that it already showed. esbuild lifts every shared chunk into a direct import of the entry, so all 55 first-load files are requested together right after the entry. Asset requests are counted in the QA below. ## Not in this PR - `modulepreload` hints in the HTML shell. They would let those chunks download in parallel with the entry, but the shell would need a build manifest of the entry's imports. - `emojibase` shortcodes (47 KB). The conversation list needs them to render text on first paint. - `@base-ui/react` (208 KB) is already imported per component and tree-shaken. ## QA Mock dashboard (`JUNIOR_DASHBOARD_MOCK_CONVERSATIONS=true pnpm dev`) in Playwright: - Home loads 55 asset files, none of them page or chart code. - Moving from home to System inside the app fetches 6 more chunks. System → People fetches 5 more. - SQL in the conversation still highlights. - No page errors and no 4xx responses. Automations and Settings redirect to home with dashboard auth off, the same as main, so they're covered by e2e instead. | Home | Conversation (code highlighted) | |---|---| |  |  | | System (in-app navigation, charts from a lazy chunk) | People | |---|---| |  |  | <img alt="home on mobile" src="https://junior-prod.sentry.dev/public/artifacts/2c2f7024-e8da-4534-898e-0121ad0e8b5d.png" width="300"> ## Checks - Dashboard `typecheck` and `lint` pass, and so do `file-length:check` and `dashboard-style:check`. - Dashboard e2e: 95/95 passed, run locally with the CI env unset. - Dashboard unit tests: 368/369. The one failure is `dashboard-output.test.ts`, caused by the stale `@sentry/server-utils@11.6.0` (`nitroIntegration` missing) in this sandbox. It fails the same way on main. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:local%3Aweb%3A17b6d7f555623680001f2a90 --> -- [View Session in Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3A17b6d7f555623680001f2a90/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The dashboard entry (
client-[hash].js) is 2.85 MB raw. An esbuild metafile showed about 400 KB of it comes from code the browser never runs.The saving is bigger raw than compressed because locale tables compress well. The browser still has to parse and compile less JS, which matters most on phones.
Changes
import { z } from "zod"reads theexport * as znamespace object. esbuild can't tree-shake that, so every zod locale (he, ru, ta, …, 248 KB) and the JSON-schema code stayed in the bundle.import * as z from "zod"gives the same API (zod 4 docs use it) and shakes correctly. On a one-line test bundle, the named import gives 433 KB and the namespace import 81 KB. This is a repo-wide codemod (325 files, import lines only), because one leftover named import anywhere in the browser graph brings everything back.junior/zod-namespace-import(intools/oxlint/junior) keeps it that way. It is on in the junior config, which the plugin packages andjunior-plugin-apishare, and in the dashboard config. Rootpnpm lintnow also lintsjunior-plugin-api, because its code ships in the bundle. Type-only imports are allowed. The built-inno-restricted-importscan't do this: withimportNames: ["z"]it also rejectsimport * as z.@sentry/junior/api/schemaimportedAGENT_INVOCATION_STATUSESandjuniorDestinationKindSchemafrom the Drizzle table modules. Those modules callpgTable(...)at the top level, so the tables and drizzle-orm came along. The values now live inchat/agent-invocations/statuses.tsanddb/schema/destination-kind.ts, and the table modules import them.db:generatereports no schema changes.injectWorkspacePackages,@sentry/juniorhas its own injected copy of@sentry/junior-plugin-api, so the browser loaded both. The client build now aliases the package to one resolved path.Not in this PR
Splitting routes comes next, as a separate PR with screenshots. The entry still eagerly includes recharts plus d3 and redux (~348 KB, used only by charts), every page under
src/client/pages(225 KB), and the emoji shortcode table (47 KB).Checks
pnpm typecheck,pnpm lint(including the new rule test inoxlint-rules:check), anddb:generate(no changes).dashboard-output.test.ts(nitro build:nitroIntegrationis not exported by a stale@sentry/server-utils@11.6.0in this sandbox). It's environmental and not caused by this diff; perf(dashboard): Keep the detail version when the reader marks a Conversation read #2720 reported the same failure on main.child-agents,locations/*, conversationdetail, andlist-usage(15/15).No UI change, so no screenshots.
--
View Session in Sentry