On-Demand and Mixed Rendering

By Syncfusion®, Inc. · Last updated

How on-demand rendering works, server adapters, Astro.request, Astro.url, Astro.response, Astro.redirect(), returning a Response, dynamic routes without getStaticPaths(), the prerender export, mixing static and on-demand pages, a decision guide, request security, the 500 page and streaming.

On-Demand Rendering

  • On-demand rendering = a server makes a page's HTML when a visitor asks for it.
  • Each request can give a different page: a search result, a cart, a "Hello, Asha".
  • Astro does this with an adapter and one line per page.
  • In this lesson, Asha adds pages that react to each visitor, while the rest of her site stays static.

Simple lesson: on-demand pages are made per request, so they can use the request.

A Real-World Example

  • Asha adds a search box to her blog.
  • The results depend on what the reader types, so they cannot be built ahead of time.
  • She adds the Node adapter and marks only the search page as on-demand.
  • Her posts stay static and fast. Only search runs on the server.

Press ▶ Run. Type a word in the box and press Search.

What happened:

  • export const prerender = false made the page on-demand.
  • The form sent ?q=... in the address. The page read it with Astro.url.searchParams.
  • Every search is a new request. The Terminal shows each one.
  • Lines tagged [server] come from the server, while it handles a request. A console.log in an on-demand page prints there too, not in the browser.

What You Will Learn

In this lesson, you will learn:

  1. How a request becomes a response.
  2. What adapters do, and how to add one.
  3. How to read the request and change the response.
  4. How to redirect, or return a Response yourself.
  5. How dynamic routes work without getStaticPaths().
  6. How to mix static and on-demand pages with prerender.
  7. How to choose a mode for each page.
  8. How Astro protects forms, and how to show a 500 page.

How On-Demand Rendering Works

Request, Render and Response

  1. A visitor's browser sends a request: the address, the method (GET or POST), headers and cookies.
  2. The server runs the page's component script with that request.
  3. The server sends back a response: a status code, headers and the HTML.

Animation: a request travels from the browser to the server, the server runs the page, and a response with HTML travels back.

Browser /search?q=astro

Server runs search.astro

1. Request: GET, address, cookies 3. Response: 200, HTML

2. Render

Each visit is one request and one response.

Static Files vs a Running Server

  • A static page is a file. The host sends it without running anything.
  • An on-demand page needs a running server that executes your code.
  • That server costs more and may be a little slower. Use it only where a page needs it.

Simple lesson: the server runs your page once per request, and sends back a response.

Server Adapters

What Adapters Do

  • Adapter = a package that lets Astro's server code run on a specific host.
  • Each host runs servers differently. The adapter does the translation.

Official Adapters

  • @astrojs/node = your own server, or any host that runs Node.js.
  • @astrojs/vercel, @astrojs/netlify, @astrojs/cloudflare = those hosts' serverless platforms.

Choosing an Adapter

  • Pick the adapter of the host you will deploy to.
  • Not sure yet? Start with @astrojs/node. You can switch later with one line.

Your pages do not change when you switch adapters. Only the adapter line in the config changes, so the choice is easy to undo.

Adding an Adapter with astro add

npx astro add node
  • It installs the package and sets adapter in the config:
import node from "@astrojs/node";

export default defineConfig({
  adapter: node({ mode: "standalone" }),
});
  • mode: "standalone" builds a server you start with node ./dist/server/entry.mjs.

Press ▶ Run. This page asks to be on demand, but there is no adapter. Read the Terminal, then add the adapter lines from above.

Simple lesson: one adapter per host. npx astro add <host> sets it up.

Working with Requests and Responses

Astro.request

  • Astro.request is a standard web Request object.
  • Astro.request.method = "GET", "POST" and so on.
  • Astro.request.headers.get("user-agent") = one request header.
  • await Astro.request.formData() = the fields of a submitted form.

Astro.url

  • Astro.url is a URL object for the current address.
  • Astro.url.pathname = the path, such as /search.
  • Astro.url.searchParams.get("q") = one value from the query string.

Astro.clientAddress

  • The visitor's IP address, as the server sees it. Some adapters do not provide it.

Astro.response

  • Change the response before it is sent:

    • Astro.response.status = 404;
    • Astro.response.headers.set("Cache-Control", "max-age=60");

Astro.redirect()

  • return Astro.redirect("/login"); sends the visitor somewhere else.
  • The default status is 302 (temporary). Pass 301 for a permanent move.
  • Use return, so the rest of the page does not run.

Press ▶ Run. Fill the form and send it. Then click the "old" link.

  • Send the form empty: the status becomes 400. The Terminal shows it.
  • The old link answers with a 301 redirect to /contact.

Returning a Response from a Page

  • A page can return new Response(...) instead of HTML.
  • Use it for a plain-text or JSON answer, or a custom status.
  • Example: return new Response("Not found", { status: 404 });

Fetching Fresh Data on Every Request

  • In an on-demand page, fetch() runs on each visit.
  • Readers always see the latest data. Each visit also waits for the API.

HTML Streaming

  • Streaming = the server sends the HTML in pieces, as each piece is ready.
  • Astro streams on-demand pages by default. The top of the page can show while a slow part below is still loading.
  • Put slow fetch() calls in components lower on the page, not all at the top.

Simple lesson: read Astro.request and Astro.url, change Astro.response, and return a redirect or a Response when needed.

On-Demand Dynamic Routes

Dynamic Routes Without getStaticPaths()

  • In a static page, getStaticPaths() must list every page ahead of time.
  • In an on-demand page, there is no list. Any value in the address is accepted.
  • The page reads Astro.params and looks up the data on each request.
  • If nothing is found, return a 404.

On-demand dynamic routes have no getStaticPaths(). Instead of a list, you must handle "not found" yourself.

Simple lesson: on-demand [slug] pages accept any value. Look it up, and return a 404 when it is missing.

Mixed Rendering

Why Mix

  • Most pages of a site are the same for everyone: static is fastest.
  • A few pages need the request: on-demand is needed.
  • Mixed rendering = both in one project. Static speed where possible, on-demand where needed.

The prerender Export

  • export const prerender = false; = render this page on demand.
  • export const prerender = true; = build this page ahead of time.
  • The output setting decides the default. prerender changes it for one page.

Two Ways to Mix

  • A static project with some on-demand pages = the default output: "static", plus prerender = false on those pages. Most sites look like this.
  • An on-demand project with some static pages = output: "server", plus prerender = true on pages that never change.

Press ▶ Run. Read the Terminal: ▶ pages were built now, λ pages run on each request.

Choosing the Right Mode

A Decision Guide

Ask one question for each page: when does its content change?

  1. Per visitor (their name, their cart, their settings) → on-demand.
  2. Per request (search results, query strings, form answers) → on-demand.
  3. Every few minutes (live scores, stock levels) → on-demand, perhaps with caching.
  4. Rarely (posts, docs, about pages) → static.

A Comparison Table

PointStaticOn-demandMixed
HostingAny static hostA server or serverless host with an adapterA host with an adapter
SpeedFastestDepends on the server and APIsFast where static, fresh where needed
Data freshnessAs of the last buildEvery requestEach page chooses
CostLowestHigher: code runs per visitLow: only some pages run code
Request dataNoYesOnly on on-demand pages

Simple lesson: start static. Make a page on-demand only when its content depends on the visitor or the request.

Request Security and Error Pages

Cross-Site Request Forgery Protection

  • CSRF (cross-site request forgery) = a bad website tricks a visitor's browser into sending a form to your site.
  • Astro checks this for on-demand pages. It is on by default: security.checkOrigin.
  • A form POST that comes from a different website is refused.

What the 403 Response Means

  • 403 Forbidden = "I understood the request, but I refuse it".
  • If a real form gets a 403, check that it is sent from your own site's address.

The 500 Page

  • 500 = "the server had an error".
  • Make src/pages/500.astro. Astro shows it when an on-demand page throws an error.
  • It gets an error prop. Do not show error details to visitors on a live site. Log them instead.

Press ▶ Run. The broken page throws an error, so the 500 page is shown.

On a live site, never print error.message on the 500 page. It can reveal details about your server. Show a friendly message and log the error for yourself.

Simple lesson: Astro blocks cross-site form posts with a 403. A 500.astro page catches server errors kindly.

TL;DR

  1. On-demand rendering = HTML made per request on a server.
  2. Adapter = runs Astro on your host. npx astro add node (or vercel, netlify, cloudflare).
  3. Astro.request = method, headers, formData(). Astro.url = pathname, searchParams.
  4. Astro.response = set status and headers.
  5. Astro.redirect(path, status) = return it. 302 by default.
  6. return new Response(...) = a custom answer, such as a 404.
  7. On-demand [slug] = no getStaticPaths(). Handle "not found" yourself.
  8. prerender = false for on-demand, true for static, per page.
  9. Choose = static unless the content depends on the visitor or the request.
  10. Security = checkOrigin refuses cross-site form posts with a 403. 500.astro = the error page.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Astro.redirect() without returnCalling it stops the pageWrite return Astro.redirect(...)The page keeps running and may fail
No adapterAstro has a built-in serverAdd one with astro addThe build stops
getStaticPaths() in an on-demand pageEvery dynamic page needs itOn-demand pages read Astro.paramsIt is ignored, with a warning
Not handling a missing itemEvery address has dataReturn a 404 when nothing is foundThe page crashes with a 500
Making the whole site on demandIt is more modernMix: on-demand only where neededSlower pages, higher bills
Showing error.message to visitorsIt helps usersLog it, show a friendly messageServer details leak

Check Yourself

Exercises

What to Remember

  • On-demand pages run on a server, once per request. They need an adapter.
  • Read the request with Astro.request and Astro.url. Change the answer with Astro.response.
  • return Astro.redirect() or return new Response() to answer differently.
  • Mix modes with prerender. Start static, opt in where needed.
  • Astro blocks cross-site form posts. A 500.astro page handles server errors.

Next: Astro Islands, where Asha adds interactive components from Preact, with JavaScript only where they live.