← case studies·SoftwarechameleonPre-registered · M_OSS2

Next.js App Router Transition

bundles ranks #2 in git co-change but #12 in the import graph. The infrastructure layer is more behaviorally active than the declared architecture suggests. The App Router transition introduced no hub shadow — the rewrite was clean.

25
Modules
23,444
Commits analysed
0.8995
r (import ↔ test)
0.002
p-value

What was measured

25 modules from the vercel/next.js monorepo. Three layers over the App Router transition window (2022–2025). 23,444 commits extracted from the public git history.

d1import_graph

Static TypeScript import/require edges between packages. Captures declared architecture — what each module explicitly depends on.

d2test_coupling

Modules that appear together in the same test files. A module heavily tested alongside another is structurally coupled to it in the test suite — independent of runtime import structure.

d3co_change

Files modified together in the same commit, extracted from 23,444 commits. Language-agnostic. Captures actual developer coupling — what engineers touch together when making changes.

Cross-layer hub correlation

Strongest gradient to date across all OSS experiments: Δr = 0.35 between the structural pair and the behavioral pair.

import_graph ↔ test_couplingCONFIRMED
r = 0.8995
betweenness r = 0.6595 · p = 0.002

Strong agreement: modules that are structurally central in the import graph are also heavily tested together. The declared and derived structural pictures agree.

import_graph ↔ co_changepartial
r = 0.5484
betweenness r = 0.5206 · p = 0.028

Moderate correlation — most structural hubs are also behaviorally active, but the bundle/infrastructure modules diverge.

test_coupling ↔ co_changebaseline
r = ~0.36
betweenness r = 0.3567 · p = n/s

Low correlation. Being heavily tested does not mean being heavily co-changed. Test infrastructure is insulated from day-to-day behavioral coupling.

Functional Proximity Law:r(import_graph ↔ test_coupling) = 0.8995 > r(import_graph ↔ co_change) = 0.5484. Gradient Δr = 0.35, largest confirmed across all OSS experiments to date. 95% CI: [0.7825, 0.9552]. Effect size: large (r² = 0.8091).

The infrastructure chameleon

bundles is mid-tier in the import graph (#12) and test coupling (#8) but ranks #2 in git co-change. It shows a different structural face depending on which layer you look at.

import_graph
#12
test_coupling
#8
co_change
#2
Finding — chameleon

The bundler infrastructure is touched in almost every significant commit — it's the plumbing that every App Router feature passes through. But it doesn't import much and isn't co-tested heavily because its interface is stable. High behavioral centrality, low declared centrality. The opposite of a hub shadow: high co_change rank, low import rank.

The App Router rewrite was clean

Pre-registered hypothesis h3 predicted that server_app_render — the new App Router rendering module — would be a hub in co_change as teams migrated to it. It ranked #15. The prediction was wrong. The meaning is positive.

DENIED · h3

server_app_render co_change rank: #15. Predicted: ≤ #5. Top hub: build.

Mechanism: clean abstraction boundary. The App Router was built as a properly isolated module. When you introduce a new rendering path without co-modifying a large set of adjacent modules, IRDME sees low behavioral centrality — because the abstraction is working as intended. A hub shadow in a new module would have been the warning sign. Its absence is the confirmation. IRDME doesn't just find problems — it confirms when architecture is sound.

What this means

For teams planning a framework migration
  • ·r = 0.8995 between import structure and test coupling means the two independent structural layers agree on what's important. That's a signal the architecture is coherent — not a coincidence.
  • ·bundles as a chameleon means: don't assess infrastructure coupling from the import graph alone. Its real risk is in co_change — it's touched in nearly every feature commit.
  • ·The clean server_app_render result is the metric a rewrite team wants to see: new modules with low co_change rank confirm the abstraction boundary held.
Why r = 0.8995 is the key number

The import graph is what your linter and bundler see. The test coupling layer is what your CI pipeline sees. They agree at r = 0.8995 — near-perfect correlation.

That agreement means: if a module becomes a hub in one structural view, it's almost certainly a hub in the other. The test suite is an independent validation of the architecture — and for Next.js, it validates. This is the Functional Proximity Law confirming a well-engineered codebase.

The same analysis runs on any monorepo with a TypeScript import graph and a public git history. Extraction time for 25 modules / 23k commits: under 5 minutes via irdme extract.

Hub ranking by layer

Selected modules. Rank = degree centrality position within each layer.

moduleimport_graphtest_couplingco_changearchetype
build#2#1#1universal_hub
server_base#1#2#11relay
lib#3#4#9relay
shared#4#3#12relay
client#5#5#4universal_hub
bundles#12#8#2chameleon
cli#9#9#3relay
experimental#13#6#5relay
telemetry#14#11#6chameleon
server_app_render#7#7#15relay

10 selected rows. All 25 modules in the raw output file.

Pre-registered hypotheses

CONFIRMEDh1

r(import_graph ↔ test_coupling) > r(import_graph ↔ co_change)

0.8995 > 0.5484 · Δr = 0.35

CONFIRMEDh2

r(import_graph ↔ test_coupling) > 0.4, p < 0.05

Pearson r = 0.8995 · Spearman r = 0.7027 · p = 0.002 · effect_size = large

DENIEDh3

server_app_render ranks ≤ 5 in co_change (App Router migration hub)

rank #15. Top hub: build. Mechanism: clean abstraction — the rewrite was properly isolated.

DENIEDh4

lib ranks ≤ 2 in import_graph

rank #3. Top hub: server_base. Off-by-one — the utility library is the third hub, not the second.

DENIEDh5

Coherence score at or above 50th null percentile

31.2th percentile. Mechanism: power-law degree distributions inflate null coherence, making coherence non-discriminative for large skewed graphs.

Reproduce

Pre-registration hash cb0cf4a4… committed to github.com/vladi160/preregistrations before analysis ran.

# clone source
git clone https://github.com/vercel/next.js
# extract (App Router window: 2022–2025)
irdme extract next.js --scope typescript --out nextjs_layers.json
# run
irdme examples/experiments/nextjs_oss2.json outputs/output_nextjs_oss2.json
# verify pre-registration
irdme verify-prediction examples/experiments/nextjs_oss2.json