The Laioutr frontend is a Nuxt 3 app. By default it uses server-side rendering (SSR): the server renders HTML for each request and sends it to the browser, then the app hydrates on the client so the page becomes interactive. This improves perceived performance and SEO. To get the most out of a CDN (Content Delivery Network) or edge caching, you need to know what the frontend does by default, which pages are good candidates for caching, and how to adjust or override SSR and caching in your own modules.
This page explains: (1) what the frontend does by default for SSR, (2) making SSR cacheable (what to cache, and how to handle time-sensitive or personalized content), (3) enabling SSR and HTML caching (TTL, Cache-Control, route rules), (4) caching API and data responses, and (5) how you can override these behaviours for custom handling.
The frontend does not set ssr: false. So Nuxt’s default applies: SSR is enabled. When a user opens a URL (e.g. from a link or search result), the server renders the page:
So by default, pages are server-rendered and the initial data for the page comes from the Orchestr API (which runs on the same Nitro server during SSR).
When you call a server action via useFetchAction, it uses Nuxt’s useAsyncData. That means the action runs once on the server during SSR (if the component is rendered then), and the result is transferred to the client. So you don’t double-execute the action on client load; it’s SSR-friendly by design.
Where something must run only in the browser (e.g. connecting to the Studio iframe, or using window), the platform uses SSR-safe patterns: ClientOnly (Nuxt’s built-in component), or import.meta.client checks so that code is skipped on the server. You can use the same patterns in your sections and blocks when you need client-only behaviour.
Out of the box, the frontend does not set:
routeRules) for caching or TTLSo the default is “SSR on, no HTTP/CDN caching”. To make SSR output cacheable and to cache API responses, you (or your hosting/CDN layer) need to add cache headers or route rules as described below.
For a CDN to cache SSR output, the HTML for a given request must be the same for every user (or you must include something like a cookie in the cache key and accept lower cache hit rates). So you need to decide which pages to cache and which content to keep out of SSR so that the server-rendered HTML is safe to share.
Good candidates for caching are shareable, indexable pages; bad candidates are session-specific or highly personalized pages. Pages that are typically reached via external links or search are good candidates for SSR + CDN caching:
These are the same for (or vary only by URL/locale, which you can encode in the cache key). Caching them improves performance and reduces load on your origin.
Pages that are session-specific or personalized are poor candidates for shared HTML cache:
If you cache their HTML, you risk serving one user’s data to another (e.g. cart or recommendations). So either do not cache these routes, or ensure personalized content is not in the SSR output (see below).
Data that changes often (e.g. stock, live prices) may be wrong if cached for a long time. You can:
The trade-off depends on how fresh the data must be and how long your cache TTL is.
Personalized or session-specific content (cart, wishlist, user name, recommendations per user) should not be part of the SSR output if you want one cached HTML per URL (or per URL + locale). Otherwise:
Recommended approach: Keep SSR output generic for cacheable pages. Load personalized or session-specific content on the client after hydration:
This way the CDN can cache one HTML per URL (and optionally per locale if you vary by that), and personalization still works correctly.
For every part of the UI that you defer to the client (e.g. “Products you may like”), use a skeleton (placeholder) with the same dimensions as the final content. That avoids layout shift when the real data loads and keeps Core Web Vitals and UX in good shape.
To have a CDN (or any HTTP cache) store and reuse SSR responses, you need to send Cache-Control (and optionally other headers) so caches know how long they can keep the response. In Nuxt 3 you can do this with route rules or middleware.
TTL is how long a cache may consider the response “fresh.” After that, the cache will revalidate or fetch again from the origin. Common pattern:
A typical default for HTML is something like: public, max-age=0, s-maxage=15, must-revalidate — no browser cache for the HTML, but the CDN can cache it for 15 seconds.
Option 1: Nuxt routeRules (recommended)
In your app’s nuxt.config.ts you can define routeRules (Nitro’s routeRules) to attach headers per path:
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// Default: short CDN TTL for HTML, no browser cache
'/**': {
headers: { 'Cache-Control': 'public, max-age=0, s-maxage=15, must-revalidate' },
},
// Don’t cache session-specific pages
'/checkout/**': { headers: { 'Cache-Control': 'private, no-store, no-cache' } },
'/cart': { headers: { 'Cache-Control': 'private, no-store, no-cache' } },
'/login': { headers: { 'Cache-Control': 'private, no-store, no-cache' } },
},
});
You can override per route (e.g. longer TTL for a static content page, or private, no-store for account pages). The exact paths depend on your app’s routing (page types and paths from your RC).
Option 2: Nitro routeRules
Same idea under nitro.routeRules if you prefer to keep everything in Nitro config:
export default defineNuxtConfig({
nitro: {
routeRules: {
'/**': { headers: { 'Cache-Control': 'public, max-age=0, s-maxage=15, must-revalidate' } },
'/checkout/**': { headers: { 'Cache-Control': 'private, no-store, no-cache' } },
},
},
});
Option 3: Middleware
You can also set Cache-Control in a Nuxt/Nitro middleware (or in a handler) based on the request path or other logic. Use this when you need dynamic rules (e.g. depending on locale or cookie).
If you ship a Nuxt module (e.g. a Laioutr app) and want to add or override cache behaviour:
That way, developers get your default caching for your routes but can still override globally or per route in nuxt.config.ts.
There are two different layers: (1) Orchestr’s own data cache (already built in), and (2) HTTP caching of API responses (for CDN or browser).
The Orchestr layer caches query, link, and component resolver results on the server (see Caching). So when the same query runs again (e.g. same product, same category), the result can be served from cache instead of calling the backend again. That reduces load on your commerce backend and speeds up SSR and client requests.
This is independent of CDN/HTTP caching: it’s an application-level cache inside the Nuxt/Nitro server. You configure it per query/link/resolver (TTL, cache key, strategy). No extra step is required for “caching API responses” at the Orchestr level beyond what’s in the Caching doc.
If you put a CDN in front of your frontend, the CDN can also cache API responses (e.g. requests to /api/orchestr/... or other server routes) if those responses send Cache-Control (and optionally Vary) headers. Then the traffic between the browser and your server (or CDN) can be cached; the requests made internally during SSR (server-to-itself) are typically not going through the CDN, so they don’t benefit from CDN cache unless you have a different setup (e.g. internal cache or edge).
To make specific API routes cacheable by the CDN:
public, max-age=0, s-maxage=60, must-revalidate for a 60-second CDN TTL). You can do this via routeRules for the API path or inside the route handler.Accept-Language or a custom x-currency), set the Vary header so the CDN stores separate entries per value.You can define routeRules for /api/** (or /api/orchestr/**) with the desired headers, or in your own module add rules for your API routes. Same idea as for HTML: your module can extend nuxt.options.routeRules (or nitro.routeRules) in its setup.
| What | Where | Default in Laioutr |
|---|---|---|
| SSR on/off | nuxt.config.ts → ssr: true | false | true (Nuxt default; we don’t set it) |
| HTML caching | routeRules or nitro.routeRules → headers['Cache-Control'] | Not set (no CDN TTL by default) |
| API response caching | Same: routeRules for /api/** or per-handler headers | Not set |
So: SSR is on by default; caching is off by default. To enable CDN-friendly caching, you add routeRules (or middleware) and set Cache-Control (and Vary if needed).
'/checkout/**', '/cart', '/api/orchestr/query') and set different Cache-Control (or private, no-store for non-cacheable routes).'/**' and then override specific paths with stricter or more permissive headers.If you build a Laioutr app or another Nuxt module:
window). Provide a skeleton or placeholder so layout doesn’t shift.onMounted or a client-only composable) so SSR HTML stays cacheable. Don’t put user-specific data into the initial SSR payload for cacheable pages.This gives you a basic understanding of what the frontend does by default (SSR on, no HTTP caching), how to make SSR cacheable (good vs bad pages, time-sensitive and personalized content, skeletons), how to enable SSR and API caching (TTL, Cache-Control, routeRules), and how to adjust and override in your own modules for custom SSR and caching behaviour.
PWA
Turn your Laioutr frontend into a Progressive Web App—installable on devices, with offline support and an app-like experience—using zero-config defaults and optional customization.
Tracking
Laioutr’s tracking abstraction gives you a single API to send analytics and marketing events. Use one or more tracking adapters (e.g. Google Tag Manager), optionally gated by consent, and implement your own adapters for other tools.