Nuxt module for Edge Side Includes (ESI) — cache individual Vue components independently at the CDN/reverse proxy layer (e.g., Varnish).
- Components wrapped with
useESIrender an<esi:include>tag instead of their content during SSR. - The response includes a
Surrogate-Control: content=ESI/1.0header, signaling the reverse proxy to process ESI tags. - The proxy (e.g., Varnish) fetches each fragment independently via the fragment endpoint (
/api/_fragment). - Each fragment can define its own
cache-controlheader, enabling per-component cache TTLs. - On the client side, hydration data is injected via inline scripts so Vue picks up the server-rendered state.
- Add
vuesidependency to your project
yarn add vuesi
# or
npm install vuesi- Add
vuesito themodulessection ofnuxt.config.ts
export default defineNuxtConfig({
modules: ['vuesi'],
vuesi: {
// Options (all optional)
enabled: true,
fragmentPath: '/api/_fragment',
ignoreErrors: true
}
})| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
true |
Enable or disable ESI tag generation |
fragmentPath |
string |
/api/_fragment |
URL path for the ESI fragment endpoint |
ignoreErrors |
boolean |
true |
Add onerror="continue" to <esi:include> tags |
Each component that should be independently cached must export a Vuesi object with a props function (and optionally a cacheControl string):
<template>
Bonjour {{ props.username }}
</template>
<script lang="ts">
import type { FragmentData } from 'vuesi'
export const Vuesi: FragmentData = {
cacheControl: 'public, max-age=120',
props: async () => {
const user = await $fetch('https://api.example.com/users/1')
return { username: user.name }
}
}
</script>
<script setup lang="ts">
const props = defineProps<{ username: string }>()
</script><template>
<WelcomeESI />
</template>
<script setup lang="ts">
import { useESI, resolveComponent } from '#imports'
const WelcomeESI = useESI('Welcome', resolveComponent('Welcome'))
</script>The first argument is the component name (as registered by Nuxt's auto-import). The second argument is the resolved component instance.
For development, a Docker Compose setup with Varnish is provided:
cp docker-compose.yml.dist docker-compose.yml
docker-compose upThe Varnish config in config/varnish/default.vcl enables ESI processing when the Surrogate-Control header is present.
- Props in URLs: Props passed to ESI-wrapped components are serialized into the fragment URL query string. These URLs are visible in proxy logs and cache keys. Do not pass sensitive data as props to ESI components. Use the
Vuesi.props()function to fetch sensitive data server-side instead. - Component registry: Only components registered by Nuxt's auto-import system can be loaded via the fragment endpoint. Arbitrary file paths are not accepted.
Wraps a component for ESI rendering.
name— The PascalCase component name as registered by Nuxt (e.g.,'Welcome','Comments')component— The resolved component instance (viaresolveComponent())
Returns a new component that renders <esi:include> on the server (when ESI is enabled) or the original component with hydrated props on the client.
Exported from each ESI-enabled component as Vuesi:
interface FragmentData {
cacheControl?: string
props: () => Promise<Record<string, unknown>>
}cacheControl— Optional HTTPCache-Controlheader value for this fragmentprops— Async function that returns the props to pass to the component
# Install dependencies
yarn install
# Generate type stubs
yarn dev:prepare
# Develop with the playground
yarn dev
# Build the playground
yarn dev:build
# Run ESLint
yarn lint
# Run tests
yarn testcp docker-compose.yml.dist docker-compose.yml
docker-compose upAccess the app through Varnish at http://localhost:8080.