Rocket
On this page

Request Time JavaScript Pages#

The Acme UI Docs site can keep its static Pages and add Request Time JavaScript Pages where a request should shape the response. Static Pages still build to files in dist/. Request Time JavaScript Pages run in development and need a production adapter for deployed builds.

Use this pattern for request-shaped documentation surfaces such as JSON metadata, parameterized playgrounds, and generated social images.

Concrete JavaScript Pages that can be rendered once during the build can stay static. This guide is for JavaScript Pages that need request-time behavior or route parameters.

Add component metadata#

Create one small data module so the new Pages can share the same component facts:

export const componentCatalog = [
  {
    slug: 'button',
    name: 'Button',
    status: 'stable',
    summary: 'A link-style action for navigation and setup tasks.',
    variants: ['primary', 'secondary'],
  },
  {
    slug: 'callout',
    name: 'Callout',
    status: 'preview',
    summary: 'A short notice block for guidance, warnings, and success messages.',
    variants: ['info', 'warning', 'success'],
  },
];

export function findComponent(slug) {
  return componentCatalog.find(component => component.slug === slug);
}

Keeping the catalog outside a Page lets the JSON endpoint, playground, and Open Graph image agree on names and statuses.

Add a JSON API Page#

Create src/pages/api/components.rocket.js:

import { componentCatalog } from '../../componentCatalog.js';

/** @type {import('@rocket/js/types.js').JsPage} */
export default async request => {
  const url = new URL(request.url);
  const body = JSON.stringify(
    {
      path: url.pathname,
      generatedAt: new Date().toISOString(),
      components: componentCatalog,
    },
    null,
    2,
  );

  return new Response(body, {
    headers: {
      'Content-Type': 'application/json; charset=utf-8',
    },
  });
};

export const config = {
  path: '/api/components',
  render: 'server',
  menu: false,
};

This Request Time JavaScript Page returns component metadata instead of HTML. It uses render: 'server' because it includes request-time data in the API response.

Add a parameterized playground Page#

Create src/pages/playground.rocket.js:

import { html } from 'lit';
import { ssrRender } from '@rocket/js/ssr.js';
import { layout } from '../docsLayout.js';
import { findComponent } from '../componentCatalog.js';
export { components } from '../docsLayout.js';

/** @type {import('@rocket/js/types.js').JsPage} */
export default async (request, context) => {
  const component = findComponent(context.params.componentName);

  if (!component) {
    return new Response('Component not found', { status: 404 });
  }

  context.pageData.title = `${component.name} playground`;
  context.pageData.content = html`
    <h1>${component.name} Playground</h1>
    <p>${component.summary}</p>

    <h2>Variants</h2>
    <ul>
      ${component.variants.map(variant => html`<li>${variant}</li>`)}
    </ul>

    <p>
      Metadata for this component is available from
      <a href="/api/components">/api/components</a>.
    </p>
  `;

  return new Response(await ssrRender(layout(context.pageData)), {
    headers: {
      'Content-Type': 'text/html; charset=utf-8',
    },
  });
};

export const config = {
  path: '/playground/:componentName',
  metadata: { title: 'Component playground' },
  render: 'server',
  menu: false,
};

The :componentName segment becomes context.params.componentName, so /playground/button and /playground/callout render different playground content from one Page file. Keep it out of the menu because the configured path contains a parameter instead of a concrete link target.

Add an Open Graph SVG Page#

Create src/pages/open-graph.rocket.js:

import { findComponent } from '../componentCatalog.js';

/** @type {import('@rocket/js/types.js').JsPage} */
export default async (request, context) => {
  const component = findComponent(context.params.componentName);

  if (!component) {
    return new Response('Component not found', { status: 404 });
  }

  const name = escapeSvg(component.name);
  const summary = escapeSvg(component.summary);
  const status = escapeSvg(component.status);

  const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" role="img">
  <rect width="1200" height="630" fill="#0f172a" />
  <rect x="72" y="72" width="1056" height="486" rx="28" fill="#f8fafc" />
  <text x="120" y="180" fill="#0f766e" font-family="Arial, sans-serif" font-size="36">
    Acme UI Docs
  </text>
  <text x="120" y="300" fill="#111827" font-family="Arial, sans-serif" font-size="96" font-weight="700">
    ${name}
  </text>
  <text x="120" y="380" fill="#334155" font-family="Arial, sans-serif" font-size="34">
    ${summary}
  </text>
  <text x="120" y="472" fill="#0f766e" font-family="Arial, sans-serif" font-size="30">
    Status: ${status}
  </text>
</svg>`;

  return new Response(svg, {
    headers: {
      'Content-Type': 'image/svg+xml; charset=utf-8',
      'Cache-Control': 'public, max-age=300',
    },
  });
};

function escapeSvg(value) {
  return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
}

export const config = {
  path: '/open-graph/:componentName.svg',
  render: 'server',
  menu: false,
};

This Request Time JavaScript Page returns an SVG Response instead of an HTML document. It does not call any external service or require credentials, so local development and local builds stay self-contained.

Configure the production adapter#

Request Time JavaScript Pages work in local development, but a production build needs an adapter that knows how to deploy request-time handlers. Update rocket-config.js:

import { netlify } from '@rocket/js/adapters/netlify.js';

/** @type {import('@rocket/js/types.js').RocketConfig} */
export default {
  includeGlobs: ['src/pages/**/*.rocket.{md,js}'],
  adapter: netlify(),
};

Without an adapter, rocket build stops when it finds a Page with render: 'server'. With the Netlify adapter configured, Rocket keeps static Pages in dist/ and writes the request-time handler needed for Request Time JavaScript Pages. Generating that handler locally does not require external credentials.

For production settings, generated output, and Netlify-specific verification, see Netlify Adapter.

Checkpoint#

Run the site and visit each new route:

npm run start

Then run the production build:

npm run build

The site now mixes static Pages for durable documentation and Request Time JavaScript Pages for request-shaped responses without moving either kind of Page out of src/pages/.

Next step#

Continue with Deploy to build and publish the static output, plus any adapter output your Request Time JavaScript Pages require.