docs(perf): optimizePackageImports is inert here - Turbopack makes it a no-op

Seven Mantine entry points were missing from optimizePackageImports while
core/hooks/icons-react were listed. Added all seven, rebuilt, and the client
bundle came out byte-for-byte identical: 482 files, 18050.7 KiB, same largest
chunk, same chunk hashes.

Identical output down to the hashes normally means a cached build, and
turbopackFileSystemCacheForBuild is enabled here, so it got checked rather than
written up as a null result. The Next.js docs are explicit: "This is not needed
when using Turbopack. Turbopack automatically analyzes and optimizes imports
without requiring this configuration." The build banner confirms Turbopack.

So the change is reverted and the existing three-entry list documented as dead
config, kept only as a webpack fallback. This also retires the sub-question of
whether @homarr/ui - the largest barrel at 111 importers - could be optimised
this way: it cannot, and it does not need to be.

Two notes for anyone revisiting: Next 16 with Turbopack prints no size columns in
its route table and emits no app-build-manifest.json, so per-route first-load JS
is unavailable from build output; these numbers are total bytes under
.next/static/chunks.
This commit is contained in:
Thomas Camlong committed 2026-08-13 15:36:12 +02:00
1 parent 320c51ac89
commit 43cc40eb66
4 files changed
+59 -13

No files matched your search

+9
View File
@@ -84,6 +84,15 @@ const nextConfig: NextConfig = {
"@ioredis/commands",
],
experimental: {
/**
* Dead config under Turbopack, which this app builds with: "Turbopack automatically analyzes and
* optimizes imports without requiring this configuration."
*
* Measured rather than assumed - adding the seven Mantine entry points that are missing here
* (charts, dates, dropzone, form, notifications, spotlight, tiptap) produced a client bundle that
* was byte-for-byte identical, 482 files and 18050.7 KiB in both arms, down to the chunk hashes.
* Kept only as a fallback for a webpack build; do not add to it expecting an effect.
*/
optimizePackageImports: ["@mantine/core", "@mantine/hooks", "@tabler/icons-react"],
turbopackFileSystemCacheForBuild: true,
useTypeScriptCli: true,
-1
View File
@@ -81,7 +81,6 @@ Start from `analyze-require-graph.mjs`, which found the shared subtree, and
| Lazy `mysql2` / `pg` | 21.4 MiB | Three implementations, all broken; see REJECTED.md. Structurally blocked because `createDb` must stay synchronous |
| `beszelSystemStats` at 1,156 nodes | unknown | Second-heaviest widget, but its cost is chart SVG (`<g>×317`), so reducing it changes what is drawn |
| `documents` reads 18–32 | unknown | Never investigated; each document carries overhead |
| Add 7 Mantine entry points to `optimizePackageImports` | unknown | One line: `dates`, `dropzone`, `charts`, `spotlight`, `form`, `notifications`, `tiptap` are not listed, while `core`/`hooks`/`icons-react` are. Needs two production builds and a first-load-JS diff to know whether it does anything. `@homarr/ui` is **not** eligible despite 111 importers — Next's barrel optimiser only rewrites pure re-export files, and `packages/ui/src/index.ts` also defines a runtime value |
## 4. For the clean PR
+29
View File
@@ -3,6 +3,35 @@
Each of these was implemented or configured, built, and measured. They are recorded with their
numbers so nobody repeats them, and because several looked like wins until the measurement came in.
## `optimizePackageImports` — inert, because this app builds with Turbopack
Seven Mantine entry points were absent from `experimental.optimizePackageImports` while
`@mantine/core`, `@mantine/hooks` and `@tabler/icons-react` were listed: `charts`, `dates`,
`dropzone`, `form`, `notifications`, `spotlight`, `tiptap`. Adding all seven and rebuilding:
| | baseline | all seven added |
| --- | --- | --- |
| client chunk files | 482 | 482 |
| client chunk bytes | 18050.7 KiB | 18050.7 KiB |
| largest chunk | 2341.5 KiB | 2341.5 KiB |
**Byte-for-byte identical, down to the chunk hashes.** That pattern normally means a cached build — and
`turbopackFileSystemCacheForBuild` is enabled here — so it was checked rather than written up as a null
result. The Next.js documentation is explicit: *"This is not needed when using Turbopack. Turbopack
automatically analyzes and optimizes imports without requiring this configuration."* The build banner
reads `Next.js 16.3.0-preview.9 (Turbopack)`.
So the existing three-entry list is also dead config. It is left in place as a fallback should the app
ever build with webpack again, with a comment saying not to expect anything from it.
This retires the entire "barrel imports / bundle optimization" line of enquiry for this repo, including
the sub-question of whether `@homarr/ui` — the biggest barrel at 111 importers — could be optimised.
It cannot be by that mechanism, and does not need to be.
Two measurement notes for anyone revisiting this: Next 16 with Turbopack prints **no size columns** in
its route table and emits no `app-build-manifest.json`, so per-route first-load JS is not available
from the build output. Total bytes under `.next/static/chunks` is what these numbers are.
## `--jitless` — looked like the biggest win of the entire investigation, and was fake
V8's `--jitless` removes the optimising compiler and all generated machine code. Its effect on code
+21 -12
View File
@@ -167,21 +167,30 @@ would be user-visible — `custom-api`'s user JSX, the edit modal's forms — th
The cost is real and worth stating: switching tabs in `health-monitoring` or `media-missing` now
remounts the panel. react-query still has the data cached, so it repaints rather than reloads.
### Bundle: partially satisfied
### Bundle: the rule does not apply here at all
`optimizePackageImports` already covers the three that matter by volume — `@mantine/core` (727
imports), `@mantine/hooks` (69), `@tabler/icons-react`. Seven smaller Mantine entry points are not
listed: `dates` (13 files), `dropzone` (12), `charts` (10), `spotlight` (7), `form` (4),
`notifications` (3), `tiptap` (1).
The "direct imports, not barrels" rule, and the whole `optimizePackageImports` lever behind it, is
**inert in this repo** — measured, then explained.
`@homarr/ui` is the largest barrel in the repo at 111 importers, and it is **not eligible**: Next's
barrel optimiser only rewrites files that consist purely of re-exports, and `packages/ui/src/index.ts`
also defines a runtime `uiConfiguration` value. Same for the other workspace barrels.
Seven Mantine entry points were missing from `optimizePackageImports`: `dates` (13 files), `dropzone`
(12), `charts` (10), `spotlight` (7), `form` (4), `notifications` (3), `tiptap` (1). Adding all seven
and rebuilding produced a client bundle that was **byte-for-byte identical** — 482 files, 18050.7 KiB
in both arms, down to the individual chunk hashes.
Adding the seven Mantine entry points is a one-line change, but it is left undone rather than guessed
at: it needs two production builds and a first-load-JS comparison, and everything else in this work
that shipped without a measurement was later found to be wrong. Recorded in
[NEXT-STEPS.md](./NEXT-STEPS.md) instead.
Identical output including hashes looks like a cached build, especially with
`turbopackFileSystemCacheForBuild` enabled, so it was checked instead of reported. The Next.js docs
settle it: *"This is not needed when using Turbopack. Turbopack automatically analyzes and optimizes
imports without requiring this configuration."* This app builds with Turbopack — the build banner says
so — and the config is dead weight.
Which also disposes of the sub-question underneath it. `@homarr/ui` is the largest barrel in the repo
at 111 importers, and I had noted it as ineligible because Next's barrel optimiser only rewrites pure
re-export files while `packages/ui/src/index.ts` also defines a runtime `uiConfiguration`. True, and
irrelevant: Turbopack does not use that optimiser and tree-shakes the barrel regardless.
Measuring first-load JS per route was not possible the usual way, incidentally: Next 16 + Turbopack
prints no size columns in the route table and emits no `app-build-manifest.json`. Total bytes under
`.next/static/chunks` is the substitute.
### `data = []` defaults: declined, with the reasoning