Progressive Rendering Format

The Progressive Rendering Format is an open specification that enables edge-accelerated streaming and partial rendering for any web framework. It allows you to deliver static content instantly from the edge while progressively loading dynamic content.

For an overview and background on this format, see our blog post introducing the Progressive Rendering Format.

How It Works

The Progressive Rendering Format uses a structured JSON response format to define:

  1. Static content that can be served immediately from edge nodes
  2. Dynamic content that should be fetched and streamed progressively
  3. How these components should be combined in the final response

When a request is processed through Tilda's infrastructure, our edge network:

  1. Identifies Progressive Rendering responses via the progressive-rendering-format: 1 header
  2. Parses the format and begins streaming static content immediately
  3. Fetches dynamic content from origin servers in parallel
  4. Streams the combined response progressively to the client

Format Specification

The Progressive Rendering Format is defined as a JSON structure with the following schema:

Progressive Rendering Format Schema
JSON
{
  "v1": {
    "status": 200,                                // HTTP status code
    "headers": {                                  // Headers for the final response
      "content-type": ["text/html; charset=utf-8"]
    },
    "body": [                                     // Array of body segments
      {
        "text": "<!DOCTYPE html>..."              // Static content (delivered immediately)
      },
      {
        "remoteBody": {                           // Dynamic content (fetched and streamed)
          "relativeUrl": "/api/data",
          "method": "GET",
          "forwardRequestHeaders": true,
          "additionalHeaders": {}
        }
      },
      {
        "text": "</div></body></html>"            // More static content
      }
    ]
  }
}

Properties

Body Segments

Body segments can be one of two types:

Remote Body Options

The remoteBody object supports the following properties:

remoteBody Properties
JSON
{
  "remoteBody": {
    "relativeUrl": "/api/data",                  // URL to fetch content from
    "method": "GET",                             // HTTP method (GET, POST, etc.)
    "forwardRequestHeaders": true,               // Forward original request headers
    "additionalHeaders": {                       // Additional headers to include
      "custom-header": "value"
    },
    "body": "request body content"               // Optional body for POST/PUT requests
  }
}

Request Variables

The format supports special variables that dynamically insert values from the original request:

Request Variables
JSON
{
  "remoteBody": {
    "relativeUrl": "$$requestPath$$",             // Original request path
    "method": "$$requestMethod$$",                // Original request method
    "additionalHeaders": {
      "x-original-url": "$$requestUrl$$",         // Full original URL
      "x-original-host": "$$requestHost$$"        // Original host
    }
  }
}

Standard Request Variables

VariableDescription
$$requestPath$$The path portion of the original request URL
$$requestMethod$$The HTTP method of the original request
$$requestUrl$$The full URL of the original request
$$requestQuery$$The query string of the original request (without the ?)
$$requestHost$$The host of the original request
$$requestRelativeUrl$$The path and query of the original request

Next.js PPR Specific Variables

VariableDescription
$$requestPathPrefixedWithNextPostponedResume$$Original path prefixed with /_next/postponed/resume
$$requestRelativeUrlPrefixedWithNextPostponedResume$$Original path and query prefixed with /_next/postponed/resume

Implementing the Format

To use the Progressive Rendering Format in your application:

1. Using Next.js PPR

The easiest way to get started is with Next.js PPR, which is already integrated with the Progressive Rendering Format:

Enable PPR with Next.js
Bash
tilda build nextjs --ppr=15-canary-v1

See the Next.js PPR documentation for more details.

2. Build-time Static Implementation

For maximum performance, you can pre-generate Progressive Rendering Format JSON files at build time and configure your routing to serve them with the appropriate headers:

  1. Generate Progressive Rendering Format JSON files during your build process
  2. Place these files in a static directory (e.g., .tilda/stage or your own build directory)
  3. Configure routing to serve these files with the progressive-rendering-format: 1 header

This approach is used by the Tilda CLI's tilda build nextjs --ppr command to implement Next.js PPR. It:

  1. Identifies routes marked for Partial Prerendering in the Next.js build output
  2. Extracts the static shell HTML content and dynamic content information
  3. Generates Progressive Rendering Format JSON files for each route
  4. Places them in .tilda/stage directory
  5. Configures routing to serve these files with the progressive-rendering-format: 1 header

This same approach can be used for any framework by creating a custom build process that:

Build-time Implementation Example
JavaScript
// 1. Generate Progressive Rendering Format files during build
const progressiveRenderingFile = {
  v1: {
    status: 200,
    headers: {
      'content-type': ['text/html; charset=utf-8']
    },
    body: [
      { text: '<html><head>...</head><body><div id="app">' },
      { 
        remoteBody: {
          relativeUrl: '/api/dynamic-data',
          method: 'GET',
          forwardRequestHeaders: true
        } 
      },
      { text: '</div></body></html>' }
    ]
  }
};

// 2. Write to files in your build directory
fs.writeFileSync(
  '.tilda/stage/route-name.progressiverendering.json', 
  JSON.stringify(progressiveRenderingFile)
);

// 3. Configure routing (e.g., in Tilda routing config)
const routingConfig = {
  routes: [{
    criteria: { path: { exact: '/route-path' }, method: ['GET', 'HEAD'] },
    action: {
      origin: 'static',
      headers: [['progressive-rendering-format', '1'], ['content-type', 'application/json']],
      staticFileRelativePath: 'route-name.progressiverendering.json'
    }
  }]
};

// 4. Use the Tilda CLI to deploy with your routing config and preserved stage directory
// tilda build --routingConfigJson '...' --preserveStage

Custom implementations can generate these files in the .tilda/stage directory or their own build directory, and then use the tilda build command with appropriate routing configuration and the --preserveStage flag if needed.

3. Dynamic Server Implementation

For applications that need to generate the Progressive Rendering Format dynamically at request time, your server needs to:

  1. Set the header progressive-rendering-format: 1
  2. Add a Tilda-Cache-Control header with a large max-age value (e.g., max-age=31536000) to ensure the JSON format response is cached on the CDN
  3. Return a JSON response following the Progressive Rendering Format

Important: The Tilda-Cache-Control header mentioned above applies to the HTTP response containing the JSON Progressive Rendering Format, not the inner response headers defined within the format itself. Proper caching of the format response is crucial for performance as it allows the static shell to be served directly from edge nodes.

Example server-side implementation (pseudo-code):

Dynamic Server Implementation Example
JavaScript
async function handleRequest(request) {
  // Determine which parts should be static vs. dynamic
  const staticShell = generateStaticShell();
  const dynamicContentUrl = '/api/dynamic-content';

  // Return Progressive Rendering Format response
  return new Response(JSON.stringify({
    v1: {
      status: 200,
      headers: {
        'content-type': ['text/html; charset=utf-8']
      },
      body: [
        { text: staticShell.start },
        { 
          remoteBody: {
            relativeUrl: dynamicContentUrl,
            method: 'GET',
            forwardRequestHeaders: true
          } 
        },
        { text: staticShell.end }
      ]
    }
  }), {
    headers: {
      'content-type': 'application/json',
      'progressive-rendering-format': '1',
      // This header applies to the Progressive Rendering Format response itself
      // This ensures the format definition is cached at the edge
      'tilda-cache-control': 'public, max-age=0, max-age=31536000'
    }
  });
}

Benefits

Implementing the Progressive Rendering Format provides several key advantages:

Current Limitations

Be aware of the following current limitations:

Further Resources