Commit Graph
3 Commits
Author SHA1 Message Date
Thomas Camlong ca0bb36664 docs(perf): Cache Components tested and rejected, with the numbers
Enabled cacheComponents: true on Next 16.3.0-preview.9 and built. The flag works
and the usual migration friction is absent - zero files export dynamic,
revalidate or fetchCache.

Two blockers. The [locale] dynamic root segment needs generateStaticParams,
which is cheap: the guide is explicit that one value suffices, because
dynamicParams: true now serves the App Shell for unlisted params and upgrades in
the background. Not 53 routes x 38 locales.

Past that, 111 routes fail with the same error - SqliteError: no such table:
serverSetting. Prerendering runs at build time and the root layout
unconditionally awaits auth, two DB reads and a cookie read. For a self-hosted
app whose database is created at runtime on the user's volume there is nothing to
query during a build. Deferring those reads means moving the providers that wrap
children inside Suspense, which leaves a static shell of little more than
<html><body> - the exact failure the migration guide warns about.

And the prize is ~16-21 ms: measured TTFB is ~3 ms for a route that skips the
root layout against ~19-24 ms for one that goes through it. Meanwhile the board -
the main screen - is already dynamic(..., { ssr: false }) with a skeleton, so it
already paints an instant shell and Cache Components has no server render to
unblock there.

Recorded with both caveats: the ~20 ms is warm local SQLite and would be larger
on a network database, and the useful idea inside this - not blocking the first
byte on auth plus two DB queries - can be had by streaming those reads in the
existing layout, without the migration.

Experiment fully reverted; no code changes in this commit.
2026-08-14 11:11:50 +02:00
Thomas Camlong 43cc40eb66 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.
2026-08-13 15:36:12 +02:00
Thomas Camlong c73b139dbd docs(perf): split the findings into per-subject markdown files
PHASE2-FINDINGS.md had grown past 1,200 lines mixing server memory, client rendering,
methodology and a long list of rejected experiments, which made it unusable as a
reference. Split under docs/perf/:

  README.md              headline results and the change-by-change ratio table
  SERVER-MEMORY.md       what the container's RAM is made of, layer by layer
  CLIENT-PERFORMANCE.md  dashboard cost in Chrome: DOM, fibers, images, paint
  REJECTED.md            ideas implemented, measured and abandoned, with numbers
  METHODOLOGY.md         the tools, and the measurement traps that gave wrong answers

PHASE2-FINDINGS.md is now an index so existing links keep working.

METHODOLOGY.md is the part worth keeping: every wrong number in this investigation came
from an instrument rather than the application, and each trap is written down - snapshots
cannot explain peaks, V8 truncates snapshot strings to ~1 KiB, snapshot objects are not
live handles, measuring without a forced GC measures V8's growth, taking a CDP snapshot
added 134 MiB to the process being measured, per-component React attribution silently
returns nothing on a production build, a fixed port can measure a leftover container, and
a server-side change that moves a client-side metric means the workload changed.
2026-08-10 19:08:20 +02:00