Skip to main content
Our docs redesign is live!

How Veda Is Structured

8 min read

You now understand the platform and the content model. This chapter maps that understanding onto the codebase so the repository stops looking like "a bunch of Next.js files" and starts reading like a deliberate architecture.

What you'll learn

  • How the main Veda folders divide responsibility

  • Why the route structure mirrors the content model

  • What lib/contentstack.ts and lib/pageUtils.ts are doing

  • Why the component renderer and preview layer matter so much

  • Which architectural ideas are portable beyond this specific starter

Start with the top-level shape

Veda implementation: The public Veda materials describe a structure centered around three areas:

  • app/

  • components/

  • lib/

That separation is already meaningful.

DiagramMermaid

This is the architecture at the level that matters most when you are onboarding:

  • routes ask for page-shaped data

  • utility functions normalize path and fetch logic

  • one integration layer talks to Contentstack

  • page components render the result

  • the block renderer turns modular content into concrete UI

app/

This is the Next.js App Router layer. It is where route-level behavior lives.

From the repo and public materials, you can see route shapes such as:

  • app/[[...slug]]/page.tsx for general pages

  • category routes

  • product-line and product routes under /products/...

This mirrors the content model instead of forcing every page through one identical path strategy.

components/

This layer contains reusable rendering pieces.

Two especially important ideas show up here:

  • page-level components for major content shapes

  • a modular block renderer that maps Contentstack data to React components

lib/

This is the application logic layer where Contentstack integration, data fetching, typing, filtering, and page utilities live.

lib/contentstack.ts: the shared content boundary

Veda implementation: This file is the main bridge between the frontend and Contentstack.

It is responsible for things like:

  • initializing the Delivery SDK

  • resolving region-aware endpoints

  • enabling preview configuration

  • exposing focused fetchers such as getPagegetHeadergetCategorygetProduct, and getProductLine

  • applying editable tags in preview mode

This is a strong pattern because it keeps low-level SDK setup and query concerns out of page components.

Contentstack concept: A shared integration boundary is portable. Even if you never use Next.js again, the idea of centralizing stack configuration and data access still holds.

Code example: the stack client and focused fetchers

Here is a shortened excerpt from the current Veda lib/contentstack.ts:

const region = getRegionForString(process.env.NEXT_PUBLIC_CONTENTSTACK_REGION as string)
const endpoints = getContentstackEndpoints(region, true)
export const isPreview = process.env.NEXT_PUBLIC_CONTENTSTACK_PREVIEW === "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
  }
});

What this is doing:

  • turns env vars into one shared SDK client

  • resolves the right hosts for the selected region

  • exposes a boolean isPreview flag the rest of the app can reuse

  • configures preview delivery and published delivery in one place

That last point matters. Instead of every page guessing how preview works, the app has one source of truth.

lib/pageUtils.ts: turning fetchers into a page system

Veda implementation: This file is one of the clearest signals that Veda is not just a minimal starter.

The public code shows it handling things like:

  • caching fetchers with React cache()

  • building paths from route parameters

  • centralizing metadata generation

  • exposing typed route-friendly wrapper functions

This matters because it normalizes a lot of repeated page concerns:

  • derive the URL

  • fetch the matching content

  • fetch shared header content

  • decide whether preview mode applies

  • generate metadata consistently

That is the difference between a demo and an application skeleton.

Code example: path building and unified fetch flow

This trimmed excerpt from lib/pageUtils.ts is worth studying:

export function buildPath(type: "page" | "category" | "product" | "productLine",
  params: any): string {
  switch (type) {
    case "page":
      return params.slug ? `/${params.slug.join("/")}` : "/";
    case "category":
      return `/category/${params.category}`;
    case "product":
      return `/products/${params.line}/${params.slug}`;
    case "productLine":
      return `/products/${params.line}`;
  }
}

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;
  }

  return { content, header, path, isPreview, previewType: type };
}

Why this is useful:

  • route params are normalized into a stable path string

  • shared header data is fetched once in the same utility layer

  • page components receive a consistent return shape

  • preview mode can be passed through without route files duplicating logic

The hidden benefit is architectural calm. A route file can stay focused on page behavior because path building, shared header fetching, and content-type dispatch are already standardized.

Route types map to content types

Veda implementation: Veda does not assume every entry is rendered the same way.

That decision shows up in the route structure:

  • general pages flow through the catch-all page route

  • product-related pages have their own route behavior

  • catalog experiences can own specialized fetching and rendering logic

This is a healthy pattern. It keeps your shared platform logic unified while letting important content domains have dedicated page behavior.

ComponentsRenderer.tsx: the page builder bridge

Veda implementation: The modular block renderer maps block names like herolisttwo_columnmedia, and rich_text to actual React components.

That is one of the most educational files in the whole repo because it solves a common headless CMS problem cleanly:

  • Contentstack stores structured modular data

  • React needs concrete components

  • preview mode needs wrappers and editable bindings

The renderer becomes the contract between editorial composition and frontend rendering.

Production note: Keeping that mapping centralized makes customization much safer. If you later add a new block type, you have one obvious place to wire it into the application.

Code example: the block-to-component map

This is the core idea from the current Veda renderer:

const componentMap = {
  hero: HeroComponent,
  list: ListComponent,
  two_column: TwoColumnComponent,
  media: MediaComponent,
  rich_text: RichTextComponent,
} as const;

That tiny map explains the whole page-builder approach:

  • Contentstack says which block type an editor added

  • the renderer looks up the matching React component

  • the app stays explicit about what is supported

That is one of the cleanest architectural lessons in the repo.

Code example: a page-level renderer

This excerpt from components/Pages/Page.tsx shows how simple page rendering becomes once the utility layers are in place:

export default function Page({
  page,
  header,
}: {
  page: PageProps;
  header?: MegaMenuProps;
}) {
  const components = page?.components || [];

  return (
    <>
      {header && (
        <MegaMenu header={header.header} product_lines={header.product_lines} />
      )}
      {components && (
        <ComponentsRenderer
          components={components}
          cslp={page.$}
          cslpWrapper="components"
        />
      )}
      <Footer />
    </>
  );
}

The page component is not doing raw API work. It is doing what a good page component should do:

  • render shared layout content

  • render the block list

  • pass preview bindings through to the renderer

components/Pages/Preview.tsx: a first-class editing mode

Veda implementation: Veda uses a dedicated preview wrapper component that:

  • initializes Live Preview

  • subscribes to change events

  • re-fetches the correct content type for the current route

  • swaps in the correct page-level renderer

This is a smart design choice because it prevents preview behavior from leaking into every page component in an ad hoc way.

Code example: one preview wrapper for multiple content types

Here is the part of the current preview component that shows the strategy most clearly:

async function getPreviewData(
  type: "page" | "product" | "productLine" | "category",
  path: string
) {
  switch (type) {
    case "page":
      return getPage(path);
    case "product":
      return getProduct(path);
    case "productLine":
      return getProductLine(path);
    case "category":
      return getCategory(path);
  }
}

That is a great example of orchestration without chaos. Preview is centralized, but each content type still gets its own fetcher.

Why this structure scales better than a tiny starter

A minimal starter often puts everything in a few files and calls it a day. That is okay for proving connectivity. It is not enough for understanding how a larger Contentstack app stays maintainable.

Veda's structure shows a more realistic split:

  • routes own page entry points

  • page utilities normalize route-specific concerns

  • the Contentstack integration layer owns fetching and preview setup

  • components focus on rendering

  • page-level components distinguish major content experiences

That is what makes the repository worth studying.

Portable lessons from this architecture

Contentstack concept: The specific file names are Veda-specific. The architectural lessons are portable:

  • centralize stack configuration

  • centralize reusable fetchers

  • normalize route-to-content logic

  • keep modular block mapping explicit

  • treat preview as a dedicated runtime mode

If you internalize those ideas, you can recreate the same strengths in other frameworks and codebases.

Key takeaways

  • Veda mirrors its content model in both its route structure and its data-fetching utilities

  • lib/contentstack.ts is the integration boundary for SDK setup, preview wiring, and content fetchers

  • lib/pageUtils.ts turns low-level fetchers into a consistent page system

  • ComponentsRenderer.tsx is the bridge between modular block data and React components

  • Preview.tsx keeps editing behavior centralized instead of scattering preview logic everywhere

Frequently asked questions

  • What is the purpose of the app/ folder in Veda?

    app/ contains the Next.js App Router routes and route-level behavior. Its route shapes mirror the content model (pages, categories, products) instead of forcing one generic path strategy.

  • What does lib/contentstack.ts handle?

    It centralizes Contentstack SDK initialization, region-aware endpoints, and Live Preview configuration. It also exposes focused fetchers like getPage, getHeader, getCategory, getProduct, and getProductLine.

  • Why does Veda use lib/pageUtils.ts?

    pageUtils normalizes route params into stable paths, caches fetchers, fetches shared header data, and returns a consistent data shape to page components. This reduces duplicated logic across route files.

  • What problem does ComponentsRenderer.tsx solve?

    It maps modular Contentstack block types (for example hero, list, rich_text) to concrete React components. This makes supported blocks explicit and keeps the page-builder contract centralized.

  • How is preview mode implemented in Veda?

    Preview behavior is centralized in a dedicated Preview.tsx wrapper that initializes Live Preview, listens for changes, and re-fetches the correct content type for the current route. This avoids scattering preview logic across page components.