SSR Stream Interception

How to intercept SSR stream to inject content before it is sent to the client.


Additional Content Injection in SSR

In some cases you may want to inject additional content into the SSR stream before it's sent to the client. This can be useful for injecting properties onto the document's root element (e.g., the lang attribute or a class name based on the user's request headers).

Step 1: Create a custom middleware for Waku

Create a new file in your project, for example ./src/middleware/stream-interceptor.ts:

import type { Middleware } from 'waku/config';

// key below is probably best to have at a central config location to allow for it to be changed easily
export const themeCookieKey = 'waku-color-theme';

const streamInterceptor: Middleware = () => {
  return async (ctx, next) => {
    const theme = ctx.context.theme;

    await next();

    ctx.res.headers ||= {};

    const isDocument = ctx.res.headers['content-type']?.includes('text/html');
    const lang = 'en-US';

    if (isDocument) {
      const documentContent = ctx.res.body;

      const newDocumentContent = documentContent?.pipeThrough(
        new TransformStream({
          async transform(chunk, controller) {
            const text = new TextDecoder().decode(chunk);
            const newText = text.replace(
              '<html>',
              `<html lang="${lang}" ${theme == 'dark' ? "class='dark'" : ''}>`,
            );

            controller.enqueue(new TextEncoder().encode(newText));
          },
        }),
      );

      ctx.res.body = newDocumentContent!;
    }
  };
};

export default streamInterceptor;

Step 2: Register the middleware

In your waku.config.ts file, import the middleware and add it to the middleware array:

import streamInterceptor from './src/middleware/stream-interceptor';
import { defineConfig } from 'waku/config';

export default defineConfig({
  // ...
  middleware: () => [
    import('waku/middleware/context'),
    import('./src/middleware/themePreload.js'), // + this is the one we just added
    import('waku/middleware/dev-server'),
    import('waku/middleware/handler'),
  ],
});

Step 3: Restart the server

After adding the middleware, you'll need to restart the server for the changes to take effect.

Step 4: Verify the changes

After restarting the server, you can verify that the changes have taken effect by inspecting the HTML source of your page. You should see the lang attribute and, if your system is set to dark mode, the class attribute on the <html> element as well.