Technical Deep Dive
How We Built originalobjective.com: Headless CMS with a Modern React Frontend
A deep dive into how we built our own website using a headless CMS with a React frontend. Covers the architecture decisions, block grid rendering, type-safe API integration, and the lessons we learned along the way.


Curated by Matt Perry
CTO
Why Headless Umbraco?
We have been building with Umbraco for years. It is a CMS we know inside out. But for our own site, we wanted something different from the traditional server-rendered approach.
The requirements were straightforward. We needed a content-managed website that loads fast, ranks well in search engines, and gives editors full control over page layouts through the block grid. We also wanted to use React and Tailwind CSS on the frontend because that is where our team works fastest.
Umbraco with its Delivery API gave us exactly that. The CMS handles content modelling, media management, and the block grid editor. Next.js handles rendering, routing, and performance. The two communicate through a typed REST API.
The Architecture
The setup is simple in principle:
Umbraco runs on .NET and serves as the content repository. Editors use the backoffice to create pages, manage block grids, upload media, and publish content. The Delivery API exposes all of this as JSON.
Next.js with the App Router fetches content from the Delivery API at build time and request time. Pages are statically generated where possible, with incremental static regeneration (ISR) keeping content fresh without full rebuilds.
Typed API clients are auto-generated from the Umbraco API specification. This means we get full type safety across the boundary between CMS and frontend. When a content type changes in Umbraco, we regenerate the client and TypeScript catches any mismatches at compile time.
Block Grid Rendering
The block grid is the core of the editing experience. Editors arrange content blocks in a 12-column grid, choosing from a library of block types we have built. Each block has content properties and optional settings properties for styling.
On the frontend, a central component mapper takes a block's content type alias and returns the correct React component. Each block component receives its content and settings as typed props.
The block grid wrapper iterates over the layout, resolves each block's component, and renders them with their content and settings. Settings typically control colours, backgrounds, and layout options, keeping styling separate from content.
Server Components by Default
Most of our block components are React Server Components. They render on the server with zero client-side JavaScript. This is a significant performance win because the majority of our blocks are static content: headings, text, images, and cards.
We only use client components where genuine interactivity is needed: forms, tabbed interfaces, search functionality, and animations. For adding animations to server components without making them client components, we built animation wrapper components that handle the motion logic on the client while keeping the content server-rendered.
Type-Safe API Integration
Auto-generating typed API clients from the CMS specification changed how we work. Instead of writing fetch calls by hand and hoping the response shape matches our types, we point our code generation tool at the API spec and it generates everything.
The generated client includes typed request functions, response models, and custom fetch wrappers. We added a custom fetch layer that handles error responses, caching headers, and revalidation consistently across all API calls.
Our workflow when content types change:
- Update the document type or data type in Umbraco
- Regenerate the TypeScript client
- Fix any type errors the compiler flags
- Update the block component if needed
This catches breaking changes at build time rather than in production.
Static Generation and ISR
Next.js changed how data fetching works in recent versions. The fetch function no longer caches by default, which caught us off guard initially. Pages that should have been static were rendering dynamically on every request.
We solved this with a combination of static parameter generation for pre-rendering pages at build time and revalidation settings on our fetch calls. Content updates appear quickly without requiring a full rebuild.
Image Handling
Images are stored in cloud blob storage through Umbraco's media system. On the frontend, we use the Next.js Image component with a custom loader that constructs the correct URL for Umbraco's image processor. This gives us automatic resizing, format conversion, and lazy loading.
The image loader handles both local development and production through environment-based URL configuration.
What We Would Do Differently
If we were starting again today, we would:
Automate content type management from day one. We built the initial content types and pages manually through the backoffice. Midway through the project, we started managing content types programmatically. It is dramatically faster and less error-prone, especially for creating block types with compositions.
Understand the caching model upfront. We spent too long debugging dynamic rendering issues that came down to framework defaults. Understanding the caching model from the start would have saved days.
Build fewer, more flexible block types. Some of our blocks could have been consolidated into more configurable versions. The maintenance overhead of many small blocks adds up.
The Stack
At a high level, here is what powers the site:
- CMS: Umbraco (headless, .NET)
- Frontend: Next.js with React
- Styling: Tailwind CSS
- API integration: Auto-generated TypeScript clients
- Media: Cloud blob storage
- Analytics: GA4 via Google Tag Manager
- Cookie consent: GDPR-compliant consent management
If you are building a similar stack and have questions, get in touch. We are happy to share what we have learned.
Ready to put AI to work in your business?
Book a free 30-minute discovery call. We will discuss your goals, identify quick wins, and outline a practical plan to get started.
Book a discovery call
Curated by Matt Perry
CTO
Why Headless Umbraco?
We have been building with Umbraco for years. It is a CMS we know inside out. But for our own site, we wanted something different from the traditional server-rendered approach.
The requirements were straightforward. We needed a content-managed website that loads fast, ranks well in search engines, and gives editors full control over page layouts through the block grid. We also wanted to use React and Tailwind CSS on the frontend because that is where our team works fastest.
Umbraco with its Delivery API gave us exactly that. The CMS handles content modelling, media management, and the block grid editor. Next.js handles rendering, routing, and performance. The two communicate through a typed REST API.
The Architecture
The setup is simple in principle:
Umbraco runs on .NET and serves as the content repository. Editors use the backoffice to create pages, manage block grids, upload media, and publish content. The Delivery API exposes all of this as JSON.
Next.js with the App Router fetches content from the Delivery API at build time and request time. Pages are statically generated where possible, with incremental static regeneration (ISR) keeping content fresh without full rebuilds.
Typed API clients are auto-generated from the Umbraco API specification. This means we get full type safety across the boundary between CMS and frontend. When a content type changes in Umbraco, we regenerate the client and TypeScript catches any mismatches at compile time.
Block Grid Rendering
The block grid is the core of the editing experience. Editors arrange content blocks in a 12-column grid, choosing from a library of block types we have built. Each block has content properties and optional settings properties for styling.
On the frontend, a central component mapper takes a block's content type alias and returns the correct React component. Each block component receives its content and settings as typed props.
The block grid wrapper iterates over the layout, resolves each block's component, and renders them with their content and settings. Settings typically control colours, backgrounds, and layout options, keeping styling separate from content.
Server Components by Default
Most of our block components are React Server Components. They render on the server with zero client-side JavaScript. This is a significant performance win because the majority of our blocks are static content: headings, text, images, and cards.
We only use client components where genuine interactivity is needed: forms, tabbed interfaces, search functionality, and animations. For adding animations to server components without making them client components, we built animation wrapper components that handle the motion logic on the client while keeping the content server-rendered.
Type-Safe API Integration
Auto-generating typed API clients from the CMS specification changed how we work. Instead of writing fetch calls by hand and hoping the response shape matches our types, we point our code generation tool at the API spec and it generates everything.
The generated client includes typed request functions, response models, and custom fetch wrappers. We added a custom fetch layer that handles error responses, caching headers, and revalidation consistently across all API calls.
Our workflow when content types change:
- Update the document type or data type in Umbraco
- Regenerate the TypeScript client
- Fix any type errors the compiler flags
- Update the block component if needed
This catches breaking changes at build time rather than in production.
Static Generation and ISR
Next.js changed how data fetching works in recent versions. The fetch function no longer caches by default, which caught us off guard initially. Pages that should have been static were rendering dynamically on every request.
We solved this with a combination of static parameter generation for pre-rendering pages at build time and revalidation settings on our fetch calls. Content updates appear quickly without requiring a full rebuild.
Image Handling
Images are stored in cloud blob storage through Umbraco's media system. On the frontend, we use the Next.js Image component with a custom loader that constructs the correct URL for Umbraco's image processor. This gives us automatic resizing, format conversion, and lazy loading.
The image loader handles both local development and production through environment-based URL configuration.
What We Would Do Differently
If we were starting again today, we would:
Automate content type management from day one. We built the initial content types and pages manually through the backoffice. Midway through the project, we started managing content types programmatically. It is dramatically faster and less error-prone, especially for creating block types with compositions.
Understand the caching model upfront. We spent too long debugging dynamic rendering issues that came down to framework defaults. Understanding the caching model from the start would have saved days.
Build fewer, more flexible block types. Some of our blocks could have been consolidated into more configurable versions. The maintenance overhead of many small blocks adds up.
The Stack
At a high level, here is what powers the site:
- CMS: Umbraco (headless, .NET)
- Frontend: Next.js with React
- Styling: Tailwind CSS
- API integration: Auto-generated TypeScript clients
- Media: Cloud blob storage
- Analytics: GA4 via Google Tag Manager
- Cookie consent: GDPR-compliant consent management
If you are building a similar stack and have questions, get in touch. We are happy to share what we have learned.
Ready to put AI to work in your business?
Book a free 30-minute discovery call. We will discuss your goals, identify quick wins, and outline a practical plan to get started.
Book a discovery callFrequently Asked Questions
Why did you choose Umbraco over WordPress or Contentful for a headless CMS?
We have over a decade of experience with Umbraco and know its content modelling capabilities inside out. The block grid editor gives content editors genuine layout control without developer involvement. Unlike WordPress, Umbraco's Delivery API was purpose-built for headless use. Unlike Contentful, we host the CMS ourselves, which gives us full control over data residency and avoids per-seat pricing that scales poorly.
How does the site perform compared to a traditional server-rendered Umbraco site?
Significantly faster. Pages load in under one second on average because they are statically generated and served from edge CDN locations. A traditional Umbraco site renders each page on demand from the .NET server. Our approach pre-renders pages at build time and revalidates them periodically, so users always get a cached response. Lighthouse performance scores consistently sit above 95.
How many block types did you build, and was it too many?
We built over 40 block types. In hindsight, some could have been consolidated into more configurable versions. For example, three separate card blocks with slight layout variations could have been one block with a layout setting. We would aim for 25 to 30 highly configurable blocks if starting again. The maintenance overhead of many small blocks adds up over time.
Can this architecture work for a site that needs frequent content updates?
Yes. Incremental static regeneration (ISR) means content updates appear on the live site within 60 seconds of publishing in Umbraco, without needing a full rebuild. For sites with thousands of pages, this is far more practical than rebuilding everything each time an editor changes a comma. The combination of static generation for speed and ISR for freshness works well for most content-driven sites.
What does this kind of headless build cost for a typical business website?
A headless Umbraco site with Next.js frontend typically costs £15,000 to £40,000 for initial build, depending on the number of page types, block components, and integrations. Ongoing hosting costs are modest: around £50 to £150 per month for the CMS on Azure, plus frontend hosting on Vercel (free tier works for many sites). The investment makes sense for businesses that need fast load times, flexible content management, and a modern development workflow.
Get Your Free Umbraco Upgrade & Headless Guide
Download our practical checklist for upgrading Umbraco and going headless with Next.js. Built from 20 years of real migration experience.
Inside this free guide:

- Umbraco Upgrade Checklist, version 4 through to 17 migration paths
- Headless Architecture Guide, set up the Delivery API with Next.js
- MCP Workflow Setup, use AI to build and manage your Umbraco site
- Block Grid Best Practices, 40+ reusable component patterns
- Custom Back Office Planning, dashboards, data types, and workflows
- Integration Roadmap, connect your CRM, mobile apps, and social media
No spam. Just practical Umbraco development insights. Unsubscribe anytime.
Find out what AI could save your business
Find out what AI could save your business
Try the ROI Calculator