Modernizing Optimizely CMS: From Razor Templates to Headless Next.js

Introduction

For years, Optimizely CMS (formerly Episerver) developers have relied on Razor templates and server-side rendering (SSR) to deliver web content. While robust, the modern web demands the speed, agility, and developer experience found in frontend frameworks like Next.js. This post explores the strategic shift from a monolithic Razor architecture to a decoupled, headless approach, leveraging the power of Next.js and the Optimizely Content Delivery API. We will walk through the core implementation and best practices to ensure a smooth transition.

Step-by-Step Guide

1. Enable Content Delivery API

First, ensure your Optimizely CMS 12 instance is configured to expose content via JSON. Install the EPiServer.ContentDeliveryApi.Cms package and configure it in Startup.cs.

// CmsIv.Web/Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddContentDeliveryApi()
            .AddContentApiFullHtml()
            .AddContentApiSearch();
}

2. Create Custom API Controllers

For complex pages like the Homepage, standard REST endpoints can be chatty. Create a custom controller to aggregate data, reducing round-trips for the Next.js frontend.

// CmsIv.Web/Controllers/Api/HomepageController.cs
[Route("api/[controller]")]
[ApiController]
public class HomepageController : ControllerBase {
    [HttpGet("data")]
    public IActionResult GetData() {
        var startPage = _contentLoader.Get<SiteStartPage>(ContentReference.StartPage);
        return Ok(new {
            Title = startPage.PageTitle,
            HeroImage = _urlResolver.GetUrl(startPage.HeroImage)
        });
    }
}

3. Implement Data Fetching in Next.js

In your Next.js application, use an asynchronous fetch function to retrieve content from the CMS. Use the revalidate option to leverage Incremental Static Regeneration (ISR).

// CmsIv.Next/lib/api.ts
export async function getHomepageData() {
    const res = await fetch(`${process.env.CMS_URL}/api/homepage/data`, {
        next: { revalidate: 600 } // Revalidate every 10 minutes
    });
    if (!res.ok) throw new Error('Failed to fetch data');
    return res.json();
}

Common Errors & Solutions

  1. CORS Policy Violations: Browser security will block requests if CORS is not configured.
    • Solution: Add [EnableCors("AllowNextJsFrontend")] to your controllers and define the policy in Startup.cs allowing the Next.js origin.
  2. Missing Content Security: The API might return 401/403 for anonymous requests.
    • Solution: Ensure "Everyone" has "Read" access to your start nodes, or implement OAuth/OpenID Connect for secure fetching.
  3. Draft vs Published URL: Attempting to fetch a slug that exists but isn't published.
    • Solution: Always check the Status property in the CMS and ensure your API filters for VersionStatus.Published.

Best Practices

  • Do: Use TypeScript interfaces to map CMS models, ensuring type safety across the stack.
  • Do: Implement robust logging in your API utility (e.g., using pino or winston) to trace fetch failures.
  • Don't: Fetch entire content graps if you only need summary data. Keep your JSON payloads lean for edge performance.

Verification

To verify the migration, navigate to your Next.js local environment (usually http://localhost:3000). Inspect the network tab to ensure requests are hitting api.ginbok.com and returning a 200 OK with the expected JSON structure.

Next Steps

Once the foundation is set, consider integrating Optimizely Graph for advanced querying or Cloudflare Workers to cache your API responses at the edge.

← Quay lại Blog
Modernizing Optimizely CMS: From Razor Templates to Headless Next.js - Ginbok