Using Sanity Content Lake CDN-backed GROQ with read‑only tokens for public SSR
Guide to using Sanity Content Lake’s CDN‑backed GROQ endpoint with read‑only tokens for public SSR, covering query structure, image URL parameters, trust boundaries, checks, and failure modes.
27 Mar 2026, 01:57 UTC

Requirements
Public server‑side rendering (SSR) needs a low‑latency, read‑only endpoint that can serve published content and optionally image assets. The solution must keep write tokens off the client, avoid exposing draft data, and allow cache‑friendly queries.
Smallest suitable design
Use the Sanity Content Lake GROQ endpoint https://api.sanity.io/v1//data/query/ with the ?cdn=true parameter for CDN‑backed results. Pair it with a read‑only API token that has no draft access. For images, request directly from https://cdn.sanity.io/images// and apply width, height, quality, fit, and crop parameters.
Example query (Node.js)
import sanityClient from '@sanity/client';
const client = sanityClient({
projectId: 'yourProjectId',
dataset: 'production',
apiVersion: '2023-05-03',
token: process.env.SANITY_READ_TOKEN, // read‑only, no draft scope
useCdn: true, // adds ?cdn=true
});
const groq = `*[_type == "post" && defined(slug.current)] | order(_updatedAt desc) [0..9] {
_id,
title,
"slug": slug.current,
mainImage {
asset->{
_id,
url
}
}
}`;
const posts = await client.fetch(groq);
// posts can be rendered directly in SSR
Example image URL
https://cdn.sanity.io/images/yourProjectId/production/abc123.jpg?w=800&q=75&fit=max
Trust and data boundaries
- Read token scope – Must be limited to
readon theproduction and exclude thedraftspermission. If drafts are needed, the token must never be shipped to the browser; keep it in server‑only environment variables. - Image parameters – Width, height, quality, fit, and crop should be validated (e.g., whitelist allowed ranges) before being appended to the CDN URL to prevent abusive image processing.
- GROQ injection – Never concatenate raw user input into the query string. Use parameterized queries via
@sanity/clientor an allow‑list of safe fields.
Operational checks
- Verify token roles in the Sanity project dashboard: API → Tokens → ensure the token lists only
readand nodraftsscope. - Run a query with and without
?cdn=trueand compare response headers; the CDN version should includex-sanity-cache: HITorMISSafter warm‑up. - Request an image with known parameters (e.g.,
w=400&q=60) and confirm the returned image dimensions match the request (use an image inspection tool oridentifyfrom ImageMagick). - Monitor CDN TTL (default 60 seconds) and plan for stale‑content windows; optionally purge via a webhook after content changes.
Failure modes
- Stale content: If the CDN cache has not expired, updates may not appear for up to the TTL. Mitigate by lowering TTL (if supported) or triggering a purge through a Sanity webhook.
- Draft leakage: Accidentally granting draft scope to a read token exposed in the frontend makes unpublished documents visible to anyone inspecting the bundle.
- Image DoS: Unchecked width/height values can cause the CDN to generate extremely large images, increasing bandwidth and processing cost.
- Query injection: Poorly sanitized user‑provided filters could alter the GROQ intent, leading to data exposure or excessive load.
Conditions that would change the design
- If real‑time content is required (no acceptable stale window), bypass the CDN (
useCdn: false) and accept higher latency. - When preview mode needs to show drafts to authenticated users, create a separate server‑side endpoint that uses a token with draft scope and never expose that token to the client.
- If image transformations must be performed by a custom service (e.g., watermarking), proxy through your own image service instead of using the Sanity CDN directly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.