Skip to main content
Our docs redesign is live!

Fetching Content, Routing, and Rendering

8 min read

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 pagecategoryproduct, 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:

  1. fetch entry data

  2. read the modular block list

  3. inspect each block's type

  4. choose the matching React component

  5. 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.

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.