Fetching Content, Routing, and Rendering
This chapter gets into the mechanics of how Veda turns Contentstack data into pages. If earlier chapters gave you the vocabulary and the architecture, this is where the moving parts connect.
What you'll learn
How the Delivery SDK is initialized in a region-aware way
How Veda fetches by content type and URL
Why cached helpers improve consistency
How metadata generation fits into the page system
How modular block data becomes React output
Which patterns are generic Contentstack practice versus Veda-specific implementation
SDK initialization
Contentstack concept: A Contentstack frontend starts by creating a stack client with the correct API key, delivery token, environment, and region.
Veda implementation: Veda goes a little further by resolving endpoints from the configured region instead of hardcoding hostnames. That is why the repo includes @timbenniks/contentstack-endpoints.
A simplified version of the idea looks like this:
import contentstack from "@contentstack/delivery-sdk";
export const stack = contentstack.stack({
apiKey: process.env.NEXT_PUBLIC_CONTENTSTACK_API_KEY!,
deliveryToken: process.env.NEXT_PUBLIC_CONTENTSTACK_DELIVERY_TOKEN!,
environment: process.env.NEXT_PUBLIC_CONTENTSTACK_ENVIRONMENT!,
region: process.env.NEXT_PUBLIC_CONTENTSTACK_REGION!,
});Veda expands that pattern with region parsing, explicit hosts, and preview-aware configuration.
Code example: the real Veda stack setup
This shortened excerpt from the current Veda repo shows the actual pattern:
const region = getRegionForString(process.env.NEXT_PUBLIC_CONTENTSTACK_REGION as string)
const endpoints = getContentstackEndpoints(region, true)
export const stack = contentstack.stack({
apiKey: process.env.NEXT_PUBLIC_CONTENTSTACK_API_KEY as string,
deliveryToken: process.env.NEXT_PUBLIC_CONTENTSTACK_DELIVERY_TOKEN as string,
environment: process.env.NEXT_PUBLIC_CONTENTSTACK_ENVIRONMENT as string,
region: region ? region : process.env.NEXT_PUBLIC_CONTENTSTACK_REGION as any,
host: process.env.NEXT_PUBLIC_CONTENTSTACK_CONTENT_DELIVERY || endpoints && endpoints.contentDelivery,
live_preview: {
enable: process.env.NEXT_PUBLIC_CONTENTSTACK_PREVIEW === "true",
preview_token: process.env.NEXT_PUBLIC_CONTENTSTACK_PREVIEW_TOKEN,
host: process.env.NEXT_PUBLIC_CONTENTSTACK_PREVIEW_HOST || endpoints && endpoints.preview
}
});This is worth understanding line by line:
region is derived, not trusted as an arbitrary string alone
endpoint resolution is centralized
delivery and preview configuration live on the same shared client
the rest of the app does not need to keep rebuilding this logic
Why region-aware host handling is a good idea
Production note: Hardcoded hosts are easy to forget and annoying to audit later. Region-aware endpoint resolution gives you a cleaner contract:
your environment variables declare which region you are in
the code derives the correct delivery and preview hosts
moving between supported regions becomes less error-prone
That is a great pattern to borrow even if you do not copy Veda line for line.
URL-based entry fetching
Veda implementation: One of the central Veda patterns is fetching content by URL rather than by hardcoded entry UID in page routes.
That makes sense for a site:
routes are URL-shaped
content entries store URL fields
the query layer translates from route path to matching content entry
That approach is especially visible in the page fetchers, where a URL is used to resolve the correct page, category, product, or product_line entry.
Code example: getPage() by URL
This excerpt from lib/contentstack.ts is one of the best pieces of teaching material in the repo:
export async function getPage(url: string): Promise<Page> {
const pageQuery = stack
.contentType("page")
.entry()
pageQuery.addParams({ include_all: true });
pageQuery.addParams({ include_all_depth: 2 });
const result = await pageQuery
.query()
.where("url", QueryOperation.EQUALS, url)
.find<Page>();
if (result.entries) {
const entry = result.entries[0]
if (isPreview) {
contentstack.Utils.addEditableTags(entry as EmbeddedItem, "page", true);
}
return entry
}
throw new Error(`Page not found for url: ${url}`);
}What this tells you:
Veda stores stable URLs in content
the frontend queries by that URL field
the SDK returns a result object with an entries array
Veda uses the first matching entry as the page
preview mode mutates the returned object with editable field metadata
Why Veda uses richer include behavior
Veda implementation: The public repo shows Veda adding deeper include parameters when fetching certain content types.
That is not accidental. A content-rich site often needs related content in the same query:
assets
referenced entries
modular content structures
If you fetch too narrowly, your rendering layer ends up making repeated follow-up requests or receiving incomplete data. Veda chooses to pull richer page-shaped payloads where that helps the page render in one pass.
What the result object looks like before Veda picks entries[0]
Contentstack concept: The SDK query result is not just the entry itself. It is a wrapper object that usually contains an entries array and sometimes a count.
So the conceptual shape is closer to this:
{
"entries": [
{
"title": "The Revival Collection",
"url": "/",
"components": [
{
"hero": {
"title": "The Revival Collection"
}
}
]
}],
"count": 1
}Veda then unwraps that structure and works with the first matching entry:
const entry = result.entries[0]That is why the data-fetching helpers feel simpler than the raw SDK response shape.
Cached fetch helpers
Veda implementation: lib/pageUtils.ts wraps key fetchers in React cache().
That gives the app a cleaner, more repeatable data layer. Instead of every route reinventing fetching and metadata logic, Veda normalizes it with helper functions.
Production note: Caching here is not just about speed. It is also about consistency. Reusing the same fetch helper reduces the chance that one route forgets a query parameter, a shared header request, or a preview condition.
Code example: the unified fetch helper
The current fetchData() flow in lib/pageUtils.ts is a good example of normalization:
export async function fetchData(type: "page" | "category" | "product" | "productLine",
params: any) {
const path = buildPath(type, params);
const header = await getHeaderCached();
let content;
switch (type) {
case "page":
content = await getPageCached(path);
break;
case "category":
content = await getCategoryCached(path);
break;
case "product":
content = await getProductCached(path);
break;
case "productLine":
content = await getProductLineCached(path);
break;
}
return { content, header, path, isPreview, previewType: type };
}That return shape is exactly why the route files stay readable. They do not need to know every fetch detail.
Metadata generation
Veda implementation: Veda generates metadata from fetched content instead of hardcoding route metadata separately from content.
That is a useful pattern because page metadata usually belongs to the content model:
title
description
Open Graph image
canonical page intent
Once metadata is part of your content-aware fetch layer, dynamic routes stay easier to manage.
How modular blocks become components
Veda implementation: The renderer maps block names from Contentstack to React components in one central place.
That means the rendering flow looks roughly like this:
fetch entry data
read the modular block list
inspect each block's type
choose the matching React component
render in order
A simplified sketch looks like this:
const blockMap = {
hero: Hero,
list: List,
media: Media,
};That explicit mapping is one of the best habits you can take from Veda. It keeps the page builder model understandable.
How the API shape becomes React props
Veda implementation: The components array in a page entry is transformed before rendering:
export function mapComponentsToKV(components: Components[]): ComponentKV[] {
return components.map((component) => {
const entries = Object.entries(component) as [ComponentName, any][];
const [name, props] = entries[0];
return { name, props };
});
}That means an API block like this:
{ "hero": { "title": "The Revival Collection" } }becomes a renderer-friendly shape like this:
{ "name": "hero", "props": { "title": "The Revival Collection" } }That translation step is small, but it makes the rest of the rendering logic much cleaner.
A product page example: using related data directly
Veda implementation: The components/Pages/Product.tsx file shows how the app consumes related data from the API shape:
if (entry && "product_line" in entry && entry.product_line?.[0]) {
breadcrumbLinks.push({
title: entry.product_line[0].title,
url: entry.product_line[0].url || "",
});
}
<List
reference={entry.product_line?.[0].products || []}
title={`Explore ${entry.product_line?.[0].title}`}
description={entry.product_line?.[0].description}
load_first_image_eager={true}
$={entry.$}
/>This is a great example of why richer relational responses are useful. The product page can render:
breadcrumb context
related products from the product line
more products from the current category
without a second hardcoded data source.
Route handling and content handling stay close, but not tangled
Contentstack concept: The route layer should know how to ask for the right content. It should not contain the entire content access policy inline.
Veda gets this mostly right:
routes call page utilities
page utilities call focused fetchers
fetchers talk to the SDK
components render the results
That creates a clear chain of responsibility.
The important distinction in this chapter
Here is the mental split to keep:
Contentstack concept: URL-driven fetching, structured content models, and modular block rendering are broadly portable ideas.
Veda implementation: The specific helpers, file names, block types, and route shapes are Veda's way of applying those ideas.
If you keep that distinction clear, you can learn from the repo without turning it into dogma.
Key takeaways
Veda initializes the Delivery SDK with region-aware configuration instead of relying on fragile hardcoded hosts
Page routes fetch by content-aware URLs rather than by scattered entry IDs
Rich include behavior helps Veda fetch connected page data in fewer steps
Cached page utilities improve consistency across routes
Metadata generation and modular block rendering are treated as first-class parts of the page system
Frequently asked questions
How does Veda initialize the Contentstack Delivery SDK across regions?
It derives the region from environment configuration and resolves delivery/preview hosts via centralized endpoint helpers. This avoids hardcoded hostnames and keeps preview configuration on the shared client.
How does Veda fetch entries for routes without hardcoded entry UIDs?
Routes are resolved by querying Contentstack entries using a stored URL field (for example, querying the "page" content type where "url" equals the request path). The helper then unwraps the SDK result and uses the first matching entry.
Why does Veda use include_all and include_all_depth in queries?
To fetch assets, references, and modular content in a single request when a page needs related data to render. This reduces follow-up requests and avoids incomplete payloads in the rendering layer.
What problem do cached fetch helpers solve in Veda?
Wrapping key fetchers with React cache() improves repeatability and consistency across routes. It reduces the risk of missing shared query params, preview conditions, or other fetch policies in individual route files.
How are modular blocks converted into React components?
Veda maps Contentstack modular block names to React components in a centralized block map. It also normalizes each block object into a { name, props } shape to make the renderer straightforward.