Performance1 min read

Every query in the browser is a decision you can move

A component that fetches after it mounts has quietly moved work from your build machine onto a stranger's phone — and taken your read token with it.

Jordan Hale

Jordan Hale

Front-end Engineer ·

Abstract blue gradient suggesting a request waterfall

There is a version of every component that fetches its own data. It is easy to write, it is self-contained, and it turns one prerendered page into a network waterfall that starts after the JavaScript bundle has finished parsing.

Reactive params are the trapdoor

The rule that keeps a query on the build machine is duller than it sounds: the params must be static. A reactive param makes the fetch re-run wherever the value changes, which is the browser — so paging, filtering and searching all become live requests without anyone deciding they should be.

useBlogIndex.tsVue
<script setup lang="ts">
// Fetched once, at build time, into the prerendered payload.
const { data } = await useSanityContent<BlogIndexQueryResult>(
    blogIndexQuery
)

// Paging and search are derived from it — no second request.
const matching = computed(() =>
    (data.value?.posts ?? []).filter(matchesFilters)
)
</script>

Dereference upstream, not downstream

The other half of the same rule: resolve references in the page query rather than in the child component that needs them. A navigation menu, a post's related posts and an author profile are all one arrow in a projection, and all three arrive as props.

Where the trade genuinely flips

Filtering a few hundred posts in memory is instant. Filtering fifty thousand is not, and at that point the work belongs back on a server. That is a real decision with a real threshold — which is different from arriving there by accident.

PerformanceStatic GenerationNuxt
Jordan Hale

Written by

Jordan Hale

Front-end Engineer

Jordan builds front ends that stay fast after launch, and spends most of that time deleting work the browser was never asked to do.