Running Nuxt3 with ESI
By utilizing the $nuxt.renderRoute function, we can dynamically render specific components or pages on the server-side and insert them into the cached page using ESI.
When you put Nuxt 3 behind Varnish, you usually end up with a dilemma: full-page caching works great for anonymous traffic, but anything dynamic (user info, cart, personalized banners) either busts the cache or gets stripped out. Edge-side includes (ESI) are one way out of this trap.
ESI lets you punch “holes” in otherwise cached HTML, telling Varnish to fetch fragments dynamically and stitch them into the page at the edge. It’s been around for years in enterprise CDNs, but it’s still underused in modern Jamstack setups.
How it works in practice
- Your Nuxt 3 app renders a base page (say, product listing) and inserts
<esi:include src="/fragments/user-profile" />
where personalized data should go. - Varnish caches the base page aggressively.
- On each request, Varnish evaluates the
<esi:include>
tag, fetches/fragments/user-profile
, and injects it into the final HTML.
This way, 95% of the page is served from cache, but the small personalized fragment remains dynamic.
You can expose fragment routes using Nuxt server routes (server/api
or server/routes
). Example:
// server/routes/user/profile.get.ts
export default defineEventHandler(async (event) => {
const user = await getUserFromSession(event) // custom helper
return `<div class="user-profile">Hello, ${user.name}</div>`
})
When rendering the main page, inject the ESI tag:
<template>
<div>
<h1>Welcome to the shop</h1>
<esi:include src="/fragments/user-profile" />
</div>
</template>
Varnish needs to know to parse ESI. In your default.vcl
:
sub vcl_backend_response {
if (beresp.http.Content-Type ~ "text/html") {
set beresp.do_esi = true;
}
}
This tells Varnish to parse <esi:include>
tags in HTML responses.
Lessons learned
- if your fragments hit authenticated APIs, you need to forward cookies/headers carefully.
- ESI fragments are fetched per request, so don’t abuse them. Keep them small and cacheable (short TTLs help).
- By default, Varnish strips out unknown tags. Always verify your
<esi:include>
is being parsed. Tools likevarnishlog
are your friend.
On our Nuxt 3 frontend, ESI let us cache heavy PLPs for anonymous and logged-in users alike, while still serving a fresh cart fragment. That cut our server load in half without sacrificing personalization.