Edge & Streaming

Preview — 3 of 10 questions

How do you use Nitro plugin hooks to intercept and modify server-rendered HTML responses?

javascript
// server/plugins/html-transform.ts
export default defineNitroPlugin((nitroApp) => {
  // Hook into HTML rendering — runs after Vue SSR, before sending to client
  nitroApp.hooks.hook('render:html', (html, { event }) => {
    // html object has: htmlAttrs, head, bodyAttrs, bodyAppended, body

    // Inject a nonce for CSP compliance
    const nonce = generateNonce()
    event.context.cspNonce = nonce

    // Add CSP nonce to inline scripts
    html.head = html.head.map((tag) =>
      tag.replace('<script', `<script nonce="${nonce}"`)
    )

    // Inject performance mark
    html.bodyAppended.push(
      `<script nonce="${nonce}">performance.mark('nuxt-end')</script>`
    )
  })

  // Hook into every request
  nitroApp.hooks.hook('request', async (event) => {
    const start = Date.now()

    // Attach request context
    event.context.requestId = crypto.randomUUID()
    event.context.startTime = start

    console.log(`[${event.context.requestId}] ${getMethod(event)} ${getRequestURL(event)}`)
  })

  // Hook into responses
  nitroApp.hooks.hook('afterResponse', (event) => {
    const duration = Date.now() - event.context.startTime
    console.log(`[${event.context.requestId}] completed in ${duration}ms`)
  })

  // Hook into errors
  nitroApp.hooks.hook('render:response', (response, { event }) => {
    // Modify response before sending
    if (response.headers) {
      response.headers['X-Request-Id'] = event.context.requestId
    }
  })
})
AUse afterEach hook in nuxt.config.ts — it runs after every SSR render
BOverride the _render.ts file in .nuxt/ directory with custom HTML processing
CUse defineNitroPlugin with the render:html hook to intercept and mutate the HTML output before it is sent to the client
DUse a Nuxt plugin with mode: 'server' and return a transformed HTML string

What are the specific constraints when deploying a Nuxt app to edge runtimes (Cloudflare Workers, Vercel Edge)?

javascript
// nuxt.config.ts — deploy to Cloudflare Workers
export default defineNuxtConfig({
  nitro: {
    preset: 'cloudflare'
  }
})

// What works on edge:
// ✅ Web Crypto API (crypto.subtle)
// ✅ fetch() — standard Web API
// ✅ Response, Request, Headers — standard
// ✅ Cloudflare KV, R2, D1 (via bindings)
// ✅ Environment variables via platform bindings
// ✅ TextEncoder/TextDecoder

// What DOESN'T work on Cloudflare Workers:
// ❌ fs/promises — no filesystem
// ❌ path, os — Node.js built-ins
// ❌ child_process — no process spawning
// ❌ net, http, tls — raw TCP not allowed
// ❌ WebSockets (Worker-side, except via CF's WebSocket API)

// server/api/edge-safe.ts — edge-compatible server route
export default defineEventHandler(async (event) => {
  // ✅ Use Web Crypto instead of Node crypto
  const key = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode('secret'),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign']
  )

  const signature = await crypto.subtle.sign(
    'HMAC',
    key,
    new TextEncoder().encode(JSON.stringify({ userId: '123' }))
  )

  // ✅ Web standard base64
  return { token: btoa(String.fromCharCode(...new Uint8Array(signature))) }
})

// nuxt.config.ts — mark dependencies as Node.js-only (exclude from edge bundle)
export default defineNuxtConfig({
  nitro: {
    preset: 'cloudflare',
    rollupConfig: {
      external: ['pg', 'ioredis'] // These cannot run on edge — use REST APIs instead
    }
  }
})
AEdge runtimes support all Node.js APIs — they are identical to Node.js servers
BEdge functions cannot access environment variables — use client-side storage instead
CEdge runtimes only support static file serving — no server-side logic is possible
DEdge runtimes run V8 isolates — they lack Node.js built-ins (no fs, net, child_process), have limited CPU time, constrained memory, and require all code to be bundled into a single script

What is streaming SSR and how does it improve perceived performance?

javascript
// Nuxt 3 streaming is handled by Nitro — here's the underlying concept:

// Traditional SSR flow:
// 1. Server waits for ALL async data (useFetch, useAsyncData)
// 2. Renders FULL HTML string
// 3. Sends ENTIRE response at once
// Time to first byte (TTFB) = slowest data source

// Streaming SSR flow:
// 1. Server renders HTML SHELL immediately (header, layout)
// 2. Sends shell to browser → browser starts rendering, loads CSS/JS
// 3. As async data resolves, streams additional HTML chunks
// 4. Browser seamlessly inserts chunks into correct positions

// Nuxt config — enable experimental streaming
export default defineNuxtConfig({
  experimental: {
    inlineSSRStyles: true // Inline critical CSS for faster FCP
  }
})
AStreaming SSR splits the JavaScript bundle into chunks that download in parallel
BStreaming compresses the SSR HTML using gzip before transmission
CStreaming SSR sends HTML to the browser progressively as Vue renders each component tree segment — allowing browsers to start rendering and fetching critical resources before the full HTML is ready
DStreaming SSR is the same as ISR — both involve incremental page generation

Sign up free to play

Answer all 10 questions (7 more), see explanations for every answer, and track your score.