100% static websites: Nuxt+Strapi
Static websites without runtime CMS dependencies allow for better performance and reliability. In this post I share how I approach this using Nuxt and Strapi.
The big picture
The architectural approach remains the same regardless of your stack. Instead of fetching data and assets from the CMS at runtime, you do this at buildtime and bundle them with the frontend.
Then, when content changes in the CMS, a rebuild of the static site is triggered via a webhook. This is a common feature of headless CMS' and static hosting platforms.
Benefits of this approach
- Improved reliability. Website builds are fully independent from the CMS instance. CMS downtime does not affect production websites. Great when selfhosting the CMS!
- Cost efficiency. Your CMS instance only need sufficient resources to serve the website's build pipeline, so it does not have to scale with the amount of website traffic.
- Reduced latency and UX. All your website assets and content is served through the same CDN together as a part of your frontend bundle. Improving FCP and LPC user experience metrics.
Downsides for this approach
- Frequently changing content. If your content changes frequently you'll be spinning up loads of CI jobs as each content change requires a rebuild of the site. Might get expensive!
- Longer build times. The amount of content in your site directly affect time needed to build a production release of your website. Can be frustrating for sites with tons of content!
Implementation
From here, this post will focus on implementing this approach with Nuxt. The approach remains the same for other headless CMS', but I'll be integrating with Strapi here.
Bundling CMS data
Now we'll focus on bundling CMS data, which for our blog example will be contents of each blogpost (eg. titles, description and bodytext). We'll be leveraging two Nuxt features to achieve this.
Firstly, we'll make sure to consume CMS data using the useAsyncData composable. Here I'm also using strapi-sdk-js to query the posts collection on the Strapi instance.
<script setup lang="ts">
const strapi = useStrapi()
const { data } = await useAsyncData(
() => strapi.find('posts'),
)
</script>
Next, we enable the payloadExtraction feature flag in our Nuxt configuration. This feature hooks into useAsyncData at buildtime to extract payloads which will be included in the frontend bundle.
export default defineNuxtConfig({
// ...
experimental: {
payloadExtraction: true,
},
// ...
}
Bundling CMS assets
To finish the implementation, we'll use Nuxt's image module to also bundle media assets in our build. The module is an image optimizer that transforms and resizes images for better site performance.
The module provides a replacement component for the native img tag. To use the module, we simply replace our img tags with NuxtImg and our images will be optimized and bundled.
<template>
<NuxtImg
:v-for="post in posts"
:key="post.slug"
:src="useMediaUrl(post)"
/>
</template>
Building
Running nuxt generate builds the project for static deployments. While prerendering Nuxt will bundle our asyncrounous data calls and the image module will optimize and bundle images.
The result is a static website with no runtime CMS dependencies, which you can deploy on any static hosting provider.
Conclusion
We've explored the benefits and downsides of building fully static sites seen how effortless an implementation can be with Nuxt. I hope it gave you some inspiration or at least was an interesting read.
For a more complete example you can visit kasperjha/portfolio, where I use the same approach. Feel free to leave an issue there if you're curious about anything.
Written by Kasper Alfarnes. Published 20th of December 2025.
