vite-plugin-ssr has been renamed Vike, see migration guide.


What is preloading? Preloading denotes the practice of loading assets (JavaScript, CSS, images, etc.) before the browser discovers them in HTML/CSS/JavaScript code. That way we reduce the network round trips required before the browser starts discovering and loading all dependencies.

By default, vite-plugin-ssr automatically inject tags to our HTML, such as <script type="module" stc="script.js">, <link rel="stylesheet" type="text/css" href="style.css">, and <link rel="preload" href="font.ttf" as="font" type="font/ttf">. It does so using a preload strategy that works for most users, but we can use injectFilter() to implement a custom preload strategy.

To improve preloading performance, we can use Early Hints which vite-plugin-ssr automatically generates.

Early Hints

The Early Hints Header is the official successor of the now deprecated HTTP2/Push.

// server.js

import { renderPage } from 'vite-plugin-ssr/server'

app.get('*', async (req, res) => {
  const pageContext = await renderPage({ urlOriginal: req.originalUrl } )
  const { earlyHints } = pageContext.httpResponse
  // For exampe with Node.js 18:
  res.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) })
type PageContext = {
  httpResponse: {
    earlyHints: {
      earlyHintLink: string, // Header Line for the Early Hint Header
      assetType: "image" | "script" | "font" | "style" | null
      mediaType: string // MIME type
      src: string // Asset's URL
      isEntry: boolean // true  ⇒ asset is an entry
                       // false ⇒ asset is a dependency of an entry

Examples: $ npm init vite-plugin-ssr@latest.

See also:


If vite-plugin-ssr's default preload strategy doesn't work for us, we can customize which and where preload/asset tags are injected.

// /renderer/_default.page.server.js

export async function render(pageContext) {
  // ...

  const documentHtml = escapeInject`<!DOCTYPE html>
        <div id="page-view">${stream}</div>

  const injectFilter = (assets) => {
    assets.forEach(asset => {
      // Preload images
      if (asset.assetType === 'image') {
        asset.inject = 'HTML_BEGIN'

  return { documentHtml, injectFilter }

See API > injectFilter().