Minesh Shah (Netcel)
+7
Sep 8, 2025
visibility 2489
star star star star star
(4 votes)

Dynamic CSP Management for Headless and Hybrid Optimizely CMS with Next.js

In the evolving realm of web security, Content Security Policy (CSP) is essential for defending against XSS and injection attacks. Traditional approaches often fall short because policies are embedded in code and hard to coordinate across environments.

Optimizely CMS supports dynamic CSP in a headed setup, where the CMS renders pages. This is straightforward with third-party modules such as the Stott Security Optimizely module. Installation is simple, configuration is clear, and the headers flow with the response.

Headless and hybrid architectures introduce a gap. The frontend is separate, so CSP headers often end up fixed in the application tier. That limits agility for security and content teams.

By combining the Stott Security module with Next.js middleware, dynamic CSP becomes possible in headless and hybrid environments. Policies remain CMS-managed, even when the site is delivered through a decoupled frontend.

What we’re solving

  • Policy changes without releases: Update CSP in CMS and apply instantly.
  • Environment variations: Manage dev, staging, and production policies centrally.
  • Cross-team flow: Security and marketing can adjust policies without blocking frontend teams.

Headed vs headless

In headed Optimizely CMS, dynamic CSP is native to the page response. This remains a solid and current pattern. In headless and hybrid setups, the CMS serves content APIs while a separate frontend handles rendering. Without a bridge, CSP is usually hardcoded in that frontend. The approach below restores the same dynamic control you expect in headed.

Architecture

User Request → Next.js Middleware → Stott Security API → Optimizely CMS → Dynamic CSP Headers
  1. The user requests a page from the Next.js app.
  2. Middleware intercepts the request.
  3. Middleware calls the Stott Security endpoint to retrieve current headers.
  4. Headers are applied to the response before it is returned.

Implementation in Next.js middleware

The example below removes development-only branches. It always sources headers from CMS for consistency.

import { NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
  const baseCmsUrl = process.env.DXP_URL || "https://localhost:5000";
  const headersUrl = `${baseCmsUrl}/stott.security.optimizely/api/compiled-headers/list/`;

  // Create or reuse a Response object from your app logic prior to this point.
  // For illustration we assume you already have a 'response' to enrich.
  let response = new Response();

  try {
    const cmsResponse = await fetch(headersUrl, {
      headers: { "Content-Type": "application/json" },
      // Consider short caching and timeouts at edge to keep latency tight
    });

    if (!cmsResponse.ok) {
      // Replace with your logger
      console.error({
        status: cmsResponse.status,
        statusText: cmsResponse.statusText,
        url: headersUrl,
        path: request.nextUrl.pathname,
        context: "middleware - security headers fetch failed"
      });
      return response; // graceful fallback
    }

    const securityHeaders: Array<{ key: string; value: string }> = await cmsResponse.json();

    securityHeaders.forEach(h => response.headers.set(h.key, h.value));

    return response;
  } catch (error) {
    // Replace with your logger
    console.error({
      error,
      url: headersUrl,
      path: request.nextUrl.pathname,
      context: "middleware - security headers processing error"
    });
    return response; // fail safely
  }
}

In your production app, you’ll attach these headers to the actual page or asset response you are returning from Next.js. Keep the fetch lean, and prefer edge runtime where possible.

Key benefits

  • CMS-managed security: Update CSP without code changes. See the effect immediately.
  • Environment flexibility: Dev can be relaxed, staging can mirror production with test tools, production can stay strict.
  • Operational speed: Emergency updates and new integrations go live from CMS.
  • Resilience: If the CMS API is unavailable, the site continues with safe defaults.
  • Developer experience: Clear separation of concerns. No more per-environment hardcoding.

Practical considerations

Performance

  • Enable caching at the CMS endpoint for short periods.
  • Return compact JSON from the Stott Security module.
  • Use edge middleware for minimal latency.

Security

  • Rely on the module’s validation to prevent invalid CSP syntax.
  • Keep an audit trail of edits to support compliance.

Workflow

  • Document your policy structure in CMS.
  • Align environments through content, not code.
  • Test policy variations in staging, then promote.

Why the Stott Security module

  • Comfortable CMS UI for policy editing.
  • Validation before changes go live.
  • CSP violation reporting integration.
  • Support for multiple sites and additional security headers.

Explore the module on GitHub: GeekInTheNorth/Stott.Security.Optimizely.

Conclusion

Headed Optimizely CMS already delivers dynamic CSP with ease. The approach above brings the same control to headless and hybrid builds. Policies stay in CMS, the frontend stays decoupled, and security remains responsive to change.

With Next.js middleware, Optimizely CMS, and the Stott Security module, CSP moves from static configuration to a manageable, collaborative capability. It works with the speed of your teams and the realities of modern delivery.

Resources

Sep 08, 2025

Comments

error Please login to comment.
Latest blogs
Building a Blazor Server UI Inside the Optimizely CMS 13 Admin Shell

What I learned shipping a Blazor Server UI inside Optimizely CMS 13 — Razor components in a NuGet package, the Blazor hub next to MapContent(),...

Stanisław Szołkowski | Sep 17, 2026 |

Giving Optimizely 13 Content Types Meaningful Icons and Thumbnails

When working with an Optimizely CMS solution that has evolved over time, it is common to end up with many content types. As the number grows, the...

Pär Wissmark | Sep 16, 2026 |

force-dynamic vs connection() for Optimizely Graph

If you have built an Optimizely CMS SDK site, you already know getContentByPath on a catch-all. This post is about the part that file never says ou...

Chirag Khanna | Sep 16, 2026 |

I built a thing - nOSC & nOCP - an experiment?

I don't think I'm always that convensional . And that's where this starts... So firstly, I was lucky enough to see OCP UI extensions for Optimizey...

Matt Pallatt | Sep 15, 2026

A day in the life of an Optimizely OMVP: OptiA11y: accessibility help for Optimizely editors that refuses to lie to them

Most accessibility tooling in the CMS space has the same shape. A crawler walks your published site, renders each page, runs axe-core over the DOM,...

Graham Carr | Sep 15, 2026

OptiPowerTools.ScheduledJobsInsights: Execution History for Optimizely’s Native Scheduled Jobs

A drop-in base class and Blazor UI that records what Optimizely CMS 13 scheduled jobs actually did — logs, metrics, result summaries, and retention.

Stanisław Szołkowski | Sep 15, 2026 |