New hook `onRenderHtmlEnd()`
Description
The onAfterRenderHtml() hook is called before Vike injects assets, which works for most use cases. But it doesn't work for some use cases.
Proposal: new hook onRenderHtmlEnd() called after the HTML is fully generated (after Vike injects assets).
Use case: HTML transformers, such as critical CSS inlining https://github.com/vikejs/vike/discussions/1975.
Workaround in the meantime:
- SSG: crawl HTML files in
dist/client/and apply transformer against each HTML file. - SSR: run transformer after
renderPage().
(Note to maintainers: ideally the name should be swapped with the already existing onAfterRenderHtml().)
Need this feature, I tried to use unhead render head (I am trying to migrate to vike, and I haven’t used +Head yet in order to minimize code modifications.), but found that the onAfterRenderHtml method only has the HTML inside #app.
onAfterRenderHtml() looks like it should be called after onRenderHtml(), but it is actually called at the beginning of onRenderHtml(). It should be called onAfterRenderPageToHtml()
@Ercilan Have you seen https://vike.dev/onAfterRenderHtml#pagecontext-pagehtml-string-stream ?
only has the HTML inside #app.
No, it does have the entire HTML. (Minus the <script> and <style> tags injected by Vike.)
only has the HTML inside #app.
No, it does have the entire HTML. (Minus the
<script>and<style>tags injected by Vike.)
@brillout You can see this comment in vike-vue directly:
pageContext.{pageHtmlString,pageHtmlStream} is set by renderPageToHtml()
https://github.com/vikejs/vike-vue/blob/0eeb2ecd62fe26674a8502d22d5d2c7ef5065924/packages/vike-vue/src/integration/onRenderHtml.ts#L34-L67
onRenderHtml() returns complete HTML, but onAfterRenderHtml() does not.
// from
pageHtmlStringOrStream = pageContext.pageHtmlString or pageContext.pageHtmlStream
// return
<div id="app">${pageHtmlStringOrStream}</div>
Ah, you're right, you can't access headHtml. How about exposing it over pageContext.headHtml? Would that solve your issue?
For my own use case, this might work, but for the full functionality of unhead, it’s not enough — for example, it supports modifying htmlAttrs to set the language. It needs the entire HTML.
This is what unhead will do:
export async function transformHtmlTemplate(head: Unhead<any>, html: string, options?: RenderSSRHeadOptions) {
const { html: parsedHtml, input } = extractUnheadInputFromHtml(html)
head.push(input, { _index: 0 })
const headHtml = await renderSSRHead(head, options)
return parsedHtml
.replace('<html>', `<html${headHtml.htmlAttrs}>`)
.replace('<body>', `<body>${headHtml.bodyTagsOpen ? `\n${headHtml.bodyTagsOpen}` : ``}`)
.replace('<body>', `<body${headHtml.bodyAttrs}>`)
.replace('</head>', `${headHtml.headTags}</head>`)
.replace('</body>', `${headHtml.bodyTags}</body>`)
}
I think it’s better to provide the full HTML. That way it matches the intuitive meaning of the function name AfterRenderHtml and also makes it easier to handle (not only for unhead but also for other potential use cases).
@Ercilan Can you use the workaround mentioned here?
I think it’s better to provide the full HTML. That way it matches the intuitive meaning of the function name AfterRenderHtml and also makes it easier to handle (not only for unhead but also for other potential use cases).
I agree and I would implement this now, but it's a breaking change so I'm inclined to work on this only after more users want it (or if it's a blocker for someone which doesn't seem to be the case since there is a workaround).
@brillout I try SSG, but unhead requires using pageContext which stores the configuration that unhead reads from each page(i add it to pageContext). If I write a Vite plugin myself to handle this, I won’t be able to access it, right?
https://github.com/magne4000/vite-plugin-vercel/blob/14ffa67357eb27ee425829defdaf1efda3cbde39/packages/vike/src/plugins/prerender.ts#L40-L41
type PrerenderContext = {
pageContexts: PageContext[]
output: Output
_pageContextInit: Record<string, unknown> | null
_noExtraDir: boolean | null
_prerenderedPageContexts: PrerenderedPageContexts
}
@brillout It seems that if I’m not doing full SSG, then I get:
prerenderContext: {
isPrerenderingEnabled: true,
isPrerenderingEnabledForAllPages: false,
output: null,
pageContexts: null
}
right?
Also, I have another question: if I plan to use mostly SSG + some SPA, do I still need a Node.js server? From what I see, in this case it still requires a server folder in order to run preview.
if I plan to use mostly SSG + some SPA, do I still need a Node.js server?
https://github.com/vikejs/vike/commit/108f4b527e91b19c03cb217299bc0b0a73a5e265 https://vike.dev/pre-rendering#ssg-vs-ssr-vs-spa
https://github.com/vikejs/vike/pull/2692 https://vike.dev/globalContext#prerenderContext https://vike.dev/api#prerender https://vike.dev/api#access-globalcontext