How Veda Is Structured
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.
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 getPage, getHeader, getCategory, getProduct, 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 hero, list, two_column, media, 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.