Describe the feature
fix #13949
related issues
#14507
#21370
Motivation :
The nitro:config Nuxt hook is currently used to control the SSG behavior of a Nuxt application. While this works, this is quite limited as its not possible to pass SSG data from a nuxt hook (like nitro:config) to a component/page. Additionally, the API to define SSG/SSR logic that should happen within a page/component is limited to simple conditional checks on environment variables.
Currently, we can use nodejs process, as well as vues onServerPrefetch to define server-only logic. However, conditional logic with process is "heavy handed" and will be applied while pre-rendering AND ssr-ing, and process is technically semantically incorrect for non node runtimes. While this is the simplest approach, it also lacks flexibility.
onServerPrefetch is a "low level" utility and is coupled to the vue lifecycle hooks. This also wouldn't be the right place to add Nuxt specific logic, as this isn't available outside of setup functions.
Edit : @danielroe mentioned that there's also process.env.prerender that is set to true while pre-rendering. While this can be used to run some SSG only logic, it's still a little limited feature wise.
Proposal:
When we communicate and talk about rendering as developers, we use the terms SSG and SSR and these terms have become widely used and understood. This proposal introduce several new composable to express ssg/ssr logic :
-
onSSG can be used for SSG logic and runs at build time only. It can be used to control the nitro pre-renderer, as well as modifying the payload (the scenario of fetching n pages (such as products/blog articles) to pre-render a home page for example). Currently we have if(process.env.prerender){}, which would be similar to.
-
onSSR can be used as more explicit and semantically correct replacements of if(process.server){}
-
onClient can be used as more explicit and semantically correct replacements of if(process.client){}
These composable are flexible and could accept multiple arguments for advanced features such as caching.
We can provide additional typesafety (ie emit lint/typescript errors if some client side only stuff is used in onSSR)
We can use the argument of the first callback to access data in a natural way, such as payload data, nitro pre-renderer instance, hydration data etc
API Design :
These composable would accept a callback function as their first argument, which can be used to run logic.
onSSG(()=> {
console.log("This runs at build time only")
})
onSSR(()=> {
console.log("This runs every time the nuxt app is rendered server side")
})
onClient(() => {
console.log("This runs on client only")
})
Example :
Logic to fetch posts :
const getPosts = async () => {
const endpoint = 'https://jsonplaceholder.typicode.com/posts'
const response = await $fetch(endpoint)
const articles = response.data.slice(0, 10);
const slugs = articles.map(({ id }) => '/articles/' + id)
return { articles, slugs }
}
Currently this has to happen within a nuxt hook, in nuxt.config or in a module.
export default defineNuxtConfig({
hooks: {
async 'nitro:config'(nitro) {
if (nitro.dev) return
const { slugs } = await getPosts();
nitro.prerender.routes.push(...slugs)
}
}
})
With this proposal, this can happen in setup functions (or elsewhere, see additional thoughts).
Payload data can be passed down to other components while pre-rendering
articles/index.vue
<script setup>
onSSG(({ prerenderRoutes })=> {
const { slugs, articles } = await getPosts();
prerenderRoutes.push(...slugs)
return articles //This will be passed to the payload and keyed with the page name
})
</script>
articles/[id].vue
<script setup>
const data = useState("data", () => [])
const extra = useState("extra", () => "")
const ssg = useState("ssg", () => false)
onSSG(({ payload })=> {
const { params } = useRoute()
const preloadData = payload.articles.find(p => p.id === params) // Can access payload
data.value = preloadData
const extraData = await $fetch(preloadData.something.url)
extra.value = extraData
ssg.value = true
})
if(ssg.value === false){
// do something if not ssg
}
</script>
Additional thoughts
Alternative names
As @pi0 suggested a prefix different than on could be used :
useSSR(() => {})
renderSSR(()=>{})
nuxtSSR(()=>{})
inSSR(()=>{})
useSSG(() => {})
renderSSG(()=>{})
nuxtSSG(()=>{})
inSSG(()=>{})
useClient(() => {})
renderClient(()=>{})
nuxtClient(()=>{})
inClient(()=>{})
There's some alternative terminologies that could be used for the action itself.
- SSR => Ssr, Server
- SSG => Ssg, Prerender, Generate
- Client => CSR, Csr, Hydrate, SPA, Spa
Early returns and conditionals
While this is api is more expressive it would be great to have the ability to do early returns and to support conditionals.
Currently ...
if(process.server) return
logic()
Without early returns
onClient(() => {
logic()
})
With an API that returns a boolean
if(onSSR()) return
logic()
// Alternatively we could also have something like this
if(isSSR()) // auto-imported helper
if(globalThis.isSSR) // global
if(globalThis.nuxtRender.isSSR) // verbose global
Availability outside of setup
If onSSR, onClient and onSSG are meant to "replace" process.server and process.client and process.env.prerender, they should be available outside of setup functions (in plugins or in modules for example).
Relationship with vue lifecycle hooks
The API is intentionally similar to the vue lifecycle hooks, but they are not the same and can be used together. Perhaps we should we use a different prefix.
onClient(() => {
onMounted(() => {
// This should work
})
})
onSSG(() => {
onMounted(() => {
// this would never run and we could emit a TS error
})
})
useState and refs
The implementation might be too coupled to useState, and it could be incompatible with 3rd party libraries like Pinia/apollo/vue-query.
Would it be possible/desirable to make this work with refs ?
Caching
The 2nd argument of onSSG/onSSR could be used for caching :
onSSG(() => {
//logic
},
{ //caching settings/logic.
})
However caching is complicated and I don't want this proposal to be too verbose.
Additional information
Final checks
Describe the feature
fix #13949
related issues
#14507
#21370
Motivation :
The
nitro:configNuxt hook is currently used to control the SSG behavior of a Nuxt application. While this works, this is quite limited as its not possible to pass SSG data from a nuxt hook (like nitro:config) to a component/page. Additionally, the API to define SSG/SSR logic that should happen within a page/component is limited to simple conditional checks on environment variables.Currently, we can use nodejs
process, as well as vuesonServerPrefetchto define server-only logic. However, conditional logic withprocessis "heavy handed" and will be applied while pre-rendering AND ssr-ing, andprocessis technically semantically incorrect for non node runtimes. While this is the simplest approach, it also lacks flexibility.onServerPrefetchis a "low level" utility and is coupled to the vue lifecycle hooks. This also wouldn't be the right place to add Nuxt specific logic, as this isn't available outside of setup functions.Edit : @danielroe mentioned that there's also
process.env.prerenderthat is set to true while pre-rendering. While this can be used to run some SSG only logic, it's still a little limited feature wise.Proposal:
When we communicate and talk about rendering as developers, we use the terms SSG and SSR and these terms have become widely used and understood. This proposal introduce several new composable to express ssg/ssr logic :
onSSGcan be used for SSG logic and runs at build time only. It can be used to control the nitro pre-renderer, as well as modifying the payload (the scenario of fetching n pages (such as products/blog articles) to pre-render a home page for example). Currently we haveif(process.env.prerender){}, which would be similar to.onSSRcan be used as more explicit and semantically correct replacements ofif(process.server){}onClientcan be used as more explicit and semantically correct replacements ofif(process.client){}These composable are flexible and could accept multiple arguments for advanced features such as caching.
We can provide additional typesafety (ie emit lint/typescript errors if some client side only stuff is used in
onSSR)We can use the argument of the first callback to access data in a natural way, such as payload data, nitro pre-renderer instance, hydration data etc
API Design :
These composable would accept a callback function as their first argument, which can be used to run logic.
Example :
Logic to fetch posts :
Currently this has to happen within a nuxt hook, in nuxt.config or in a module.
With this proposal, this can happen in setup functions (or elsewhere, see additional thoughts).
Payload data can be passed down to other components while pre-rendering
articles/index.vue
articles/[id].vue
Additional thoughts
Alternative names
As @pi0 suggested a prefix different than
oncould be used :There's some alternative terminologies that could be used for the action itself.
Early returns and conditionals
While this is api is more expressive it would be great to have the ability to do early returns and to support conditionals.
Currently ...
Without early returns
With an API that returns a boolean
Availability outside of setup
If
onSSR,onClientandonSSGare meant to "replace"process.serverandprocess.clientandprocess.env.prerender, they should be available outside of setup functions (in plugins or in modules for example).Relationship with vue lifecycle hooks
The API is intentionally similar to the vue lifecycle hooks, but they are not the same and can be used together. Perhaps we should we use a different prefix.
useState and refs
The implementation might be too coupled to useState, and it could be incompatible with 3rd party libraries like Pinia/apollo/vue-query.
Would it be possible/desirable to make this work with refs ?
Caching
The 2nd argument of onSSG/onSSR could be used for caching :
However caching is complicated and I don't want this proposal to be too verbose.
Additional information
Final checks