From ae6a5ae079fc7053ef186c14a8e4ebbd7c28cb71 Mon Sep 17 00:00:00 2001 From: Manuel Schiller <6340397+schiller-manuel@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:52:55 +0200 Subject: [PATCH] perf(vue-router): remove redundant match fragment Remove the inner Fragment around scroll restoration while retaining the outer Fragment, script/null condition and fresh shell-slot VNodes. Cover child state across shell updates/invalidation and SSR script selectors/nonces. Native malloc profiling on macOS arm64, Node 24.8.0, with the existing CodSpeed flags, seven preparation calls and three fresh final captures: - Interrupted peak after middleware split: 733736 -> 295024 bytes (-59.79%). - Navigation peak after middleware split: 420776 -> 420896 (+120 bytes). - Composed vs f2530bf453: interrupted -59.79%, navigation -19.32%, unique-location churn effectively unchanged (-0.016%). These are local native measurements, not a Linux CodSpeed CI result. Navigation end-of-region outstanding malloc increases 12472 bytes versus the middleware-only control; this endpoint is before teardown, not retained JS heap. Total allocated native bytes decrease. Standalone Vue canonical Link CPU: 5.32037 -> 5.30865 ms/batch, -0.22% [-4.04%, +3.75%], inconclusive across four isolated process pairs. Incremental gzip: Vue Router minimal/full +1/+1 byte, Vue Start 0/-2. All four Vue bundles remove 12 raw bytes; other 14 fixtures are identical. No benchmark flags, workload counts, public APIs or features changed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .changeset/cyan-beers-kneel.md | 5 ++ packages/vue-router/src/Match.tsx | 8 +- packages/vue-router/tests/Match.test.tsx | 107 +++++++++++++++++++++++ 3 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 .changeset/cyan-beers-kneel.md create mode 100644 packages/vue-router/tests/Match.test.tsx diff --git a/.changeset/cyan-beers-kneel.md b/.changeset/cyan-beers-kneel.md new file mode 100644 index 0000000000..1bdbfe6ded --- /dev/null +++ b/.changeset/cyan-beers-kneel.md @@ -0,0 +1,5 @@ +--- +'@tanstack/vue-router': patch +--- + +Remove an unnecessary match Fragment to reduce VNode allocation and transient JIT compilation memory while preserving route content and scroll restoration. diff --git a/packages/vue-router/src/Match.tsx b/packages/vue-router/src/Match.tsx index 552dcc2e30..de1bcfa67a 100644 --- a/packages/vue-router/src/Match.tsx +++ b/packages/vue-router/src/Match.tsx @@ -129,11 +129,9 @@ export const Match = Vue.defineComponent({ return Vue.h(Vue.Fragment, null, [ content, - Vue.h(Vue.Fragment, null, [ - (isServer ?? router.isServer) && router.options.scrollRestoration - ? Vue.h(ScrollRestoration) - : null, - ]), + (isServer ?? router.isServer) && router.options.scrollRestoration + ? Vue.h(ScrollRestoration) + : null, ]) } diff --git a/packages/vue-router/tests/Match.test.tsx b/packages/vue-router/tests/Match.test.tsx new file mode 100644 index 0000000000..0d2e7b6f15 --- /dev/null +++ b/packages/vue-router/tests/Match.test.tsx @@ -0,0 +1,107 @@ +import * as Vue from 'vue' +import { renderToString } from 'vue/server-renderer' +import { afterEach, expect, test, vi } from 'vitest' +import { cleanup, fireEvent, render } from '@testing-library/vue' +import { + Outlet, + RouterProvider, + createMemoryHistory, + createRootRoute, + createRoute, + createRouter, +} from '../src' + +afterEach(cleanup) + +function createTestRouter( + scrollRestoration: boolean | (() => boolean), + isServer = false, +) { + const revision = Vue.ref(0) + const mounted = vi.fn() + const Child = Vue.defineComponent({ + setup() { + const count = Vue.ref(0) + Vue.onMounted(mounted) + return () => + }, + }) + const Shell = Vue.defineComponent({ + setup(_, { slots }) { + return () => ( +