Rollin Logo
Building Secure Middleware for Mobile Apps with Optimizely CMS: A 2025 Developer's Guide

Building Secure Middleware for Mobile Apps with Optimizely CMS: A 2025 Developer's Guide

Samuel Rollin
Samuel Rollin
2025-06-03
Last update: 2025-06-13
Mobile users now expect the same rich, personalized content experiences they get on desktop. But delivering content from your Optimizely CMS to mobile apps isn't as simple as serving web pages. You need a secure bridge between your CMS and mobile clients—and that's where middleware comes in.

Whether you're a developer tasked with mobile integration or a project manager planning your next mobile initiative, this guide walks through implementing secure, scalable middleware for Optimizely CMS. We'll cover everything from architecture decisions to practical code examples, with a focus on what actually works in production.

Why Middleware Matters for Mobile CMS Integration

Think of middleware as a translator between your mobile app and your CMS. Your mobile app speaks Swift or Kotlin, while your Optimizely CMS speaks C# and HTML. Without middleware, you'd need to expose your CMS directly to mobile clients—which creates security risks and tight coupling that makes future changes painful.

Middleware solves several critical problems:

Security isolation - Your mobile apps never directly access your CMS. Instead, they hit your middleware APIs, which handle authentication and authorization before fetching content.

Performance optimization - Middleware can cache responses, compress data, and transform content specifically for mobile consumption.

API consistency - Even when your CMS structure changes, your mobile apps continue working because the middleware handles the translation.

Multi-platform support - The same middleware can serve iOS apps, Android apps, and mobile web experiences.

The Current Optimizely Ecosystem in 2025

Before diving into implementation, let's establish where Optimizely stands today. The platform has evolved significantly from its Episerver roots.

Optimizely CMS 12 is the current stable version, with CMS 13 on the horizon. The big shift is toward headless content delivery through Optimizely Graph—their GraphQL API that's now the recommended approach for mobile and headless integrations.

This isn't just marketing speak. GraphQL genuinely simplifies mobile development because you can request exactly the data you need in a single call. No more making multiple REST calls to assemble a single screen's worth of content.

The other major trend is Next.js adoption. Many teams use Next.js not just for web frontends, but as middleware for mobile apps. It handles server-side rendering, static generation, and can act as an API layer between your mobile apps and Optimizely Graph.

Architecture: Designing Your Middleware Layer

Let's start with the big picture. Your architecture will likely look something like this:

Mobile Apps → Middleware API → Optimizely Graph → Optimizely CMS

The middleware sits between your mobile clients and Optimizely's GraphQL endpoint. It handles authentication, transforms data for mobile consumption, and can add caching or rate limiting.

You have two main technology choices for the middleware:

Option 1: .NET API - Build a custom Web API using ASP.NET Core. This makes sense if your team is already deep in the .NET ecosystem and you need complex business logic in your middleware.

Option 2: Next.js - Use Next.js API routes as your middleware layer. This approach is gaining popularity because it's simpler to deploy and scale, especially on platforms like Vercel.

Setting Up Optimizely Graph

Your first step is enabling Optimizely Graph for your CMS instance. This GraphQL API will be your primary way of getting content to mobile apps.

The setup process involves configuring your content types for headless delivery. Here's a typical content type that works well with mobile apps:

[ContentType(DisplayName = "Mobile Article", GUID = "your-guid-here")]
public class MobileArticlePage : PageData
{
    [Display(Name = "Title")]
    public virtual string Title { get; set; }
    
    [Display(Name = "Summary")]
    public virtual string Summary { get; set; }
    
    [Display(Name = "Body Content")]
    public virtual XhtmlString Body { get; set; }
    
    [Display(Name = "Featured Image")]
    public virtual ContentReference FeaturedImage { get; set; }
    
    [Display(Name = "Publication Date")]
    public virtual DateTime PublicationDate { get; set; }
}

The key is thinking mobile-first when designing your content types. Mobile screens are smaller, so you need concise summaries. Mobile networks can be slow, so you want to separate heavy content like images from lightweight text content.

Building Your Middleware Layer

Now for the meat of the implementation. Let's look at both approaches.

Approach 1: .NET Middleware

If you're going with a custom .NET API, your controller might look like this:

[Authorize]
[ApiController]
[Route("api/mobile")]
public class MobileContentController : ControllerBase
{
    private readonly IOptimizelyGraphService _graphService;
    private readonly IMemoryCache _cache;

    public MobileContentController(
        IOptimizelyGraphService graphService, 
        IMemoryCache cache)
    {
        _graphService = graphService;
        _cache = cache;
    }

    [HttpGet("articles")]
    public async Task GetArticles(
        [FromQuery] int page = 1, 
        [FromQuery] int pageSize = 10)
    {
        var cacheKey = $"articles_page_{page}_size_{pageSize}";
        
        if (_cache.TryGetValue(cacheKey, out var cachedArticles))
        {
            return Ok(cachedArticles);
        }

        var articles = await _graphService.GetArticlesAsync(page, pageSize);
        
        // Transform for mobile consumption
        var mobileArticles = articles.Select(a => new 
        {
            Id = a.Id,
            Title = a.Title,
            Summary = a.Summary,
            ImageUrl = a.FeaturedImage?.Url,
            PublishedDate = a.PublicationDate
        });

        _cache.Set(cacheKey, mobileArticles, TimeSpan.FromMinutes(15));
        
        return Ok(mobileArticles);
    }
}

Notice a few important details here. We're using the [Authorize] attribute to ensure only authenticated clients can access our content. We're also implementing caching to reduce load on Optimizely Graph and improve response times for mobile users.

The data transformation is crucial. We're not just passing through whatever Optimizely Graph returns. We're reshaping it specifically for mobile consumption—smaller payloads, only the fields mobile apps actually need.

Approach 2: Next.js Middleware

The Next.js approach is often simpler to implement and deploy. Here's how you might structure your GraphQL service:

// lib/optimizely.js
export const fetchOptimizelyGraph = async (query, variables = {}) => {
  const response = await fetch(process.env.OPTIMIZELY_GRAPHQL_ENDPOINT, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.OPTIMIZELY_API_TOKEN}`,
    },
    body: JSON.stringify({ query, variables }),
  });
  
  if (!response.ok) {
    throw new Error(`GraphQL request failed: ${response.statusText}`);
  }
  
  return response.json();
};

And your API route:

// pages/api/mobile/articles.js
import { fetchOptimizelyGraph } from '../../../lib/optimizely';

export default async function handler(req, res) {
  if (req.method !== 'GET') {
    return res.status(405).json({ message: 'Method not allowed' });
  }

  const { page = '1', pageSize = '10' } = req.query;
  
  const query = `
    query GetArticles($offset: Int, $limit: Int) {
      Content(
        where: { ContentType: { eq: "MobileArticlePage" } }
        offset: $offset
        limit: $limit
        orderBy: { PublicationDate: DESC }
      ) {
        items {
          Id
          Title
          Summary
          FeaturedImage {
            Url
          }
          PublicationDate
        }
      }
    }
  `;

  try {
    const data = await fetchOptimizelyGraph(query, {
      offset: (parseInt(page) - 1) * parseInt(pageSize),
      limit: parseInt(pageSize)
    });

    res.status(200).json(data.data.Content.items);
  } catch (error) {
    console.error('Error fetching articles:', error);
    res.status(500).json({ message: 'Internal server error' });
  }
}

Security: Getting It Right

Security isn't optional when you're exposing content to mobile apps. Your middleware needs multiple layers of protection.

Authentication and authorization are your first line of defense. Use OAuth 2.1 or OpenID Connect for token-based authentication. Here's a basic JWT validation middleware for Next.js:

// middleware.js
import { NextResponse } from 'next/server';
import { jwtVerify } from 'jose';

export async function middleware(request) {
  if (request.nextUrl.pathname.startsWith('/api/mobile')) {
    const token = request.headers.get('authorization')?.replace('Bearer ', '');
    
    if (!token) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
    }

    try {
      await jwtVerify(token, new TextEncoder().encode(process.env.JWT_SECRET));
    } catch (error) {
      return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
    }
  }

  return NextResponse.next();
}

Input validation prevents injection attacks. Always validate and sanitize any data coming from mobile clients before passing it to your CMS or database queries.

Rate limiting prevents abuse. You can implement this at the middleware level or use services like Cloudflare. Here's a simple in-memory rate limiter:

const rateLimiter = new Map();

function isRateLimited(ip, limit = 100, windowMs = 60000) {
  const now = Date.now();
  const userRequests = rateLimiter.get(ip) || { count: 0, resetTime: now   windowMs };
  
  if (now > userRequests.resetTime) {
    userRequests.count = 1;
    userRequests.resetTime = now   windowMs;
  } else {
    userRequests.count  ;
  }
  
  rateLimiter.set(ip, userRequests);
  return userRequests.count > limit;
}

Deployment and Scaling Considerations

Your middleware needs to handle production traffic reliably. This means thinking about deployment from day one.

CI/CD pipelines should automatically test and deploy your middleware. Here's a basic GitHub Actions workflow for a Next.js middleware:

name: Deploy Middleware
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run test
      - run: npm run build
      - uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.ORG_ID }}
          vercel-project-id: ${{ secrets.PROJECT_ID }}

Monitoring and logging help you catch issues before your users do. Use tools like Application Insights for .NET apps or services like Sentry for Next.js apps.

Secrets management keeps your API keys and connection strings secure. Use Azure Key Vault, AWS Secrets Manager, or Vercel's environment variables—never commit secrets to your repository.

Common Pitfalls and How to Avoid Them

After working with dozens of mobile CMS integrations, I've seen the same mistakes repeatedly. Here's how to avoid them.

Token expiry handling trips up many teams. Your mobile apps need to handle both access token and refresh token flows. Build this logic into your apps from the start, not as an afterthought.

CORS configuration often causes headaches during development. Configure CORS to allow only your trusted mobile app origins, not wildcard access.

Cache invalidation becomes critical when your content changes frequently. Use Optimizely's webhooks to trigger cache clearing when content updates:

// pages/api/webhook/content-updated.js
export default async function handler(req, res) {
  // Verify webhook signature
  const signature = req.headers['x-optimizely-signature'];
  // ... signature verification logic ...

  // Clear relevant caches
  await clearCache(req.body.contentId);
  
  res.status(200).json({ success: true });
}

Performance Optimization Strategies

Mobile users are impatient, and mobile networks can be unreliable. Your middleware needs to be fast.

Caching is your best friend. Cache aggressively at multiple levels—in your middleware, at the CDN level, and in your mobile apps. But remember that caching and real-time updates are often at odds with each other.

Data transformation should happen server-side, not in your mobile apps. Your middleware should return exactly the data your mobile screens need, in the format they expect.

Compression reduces bandwidth usage. Enable gzip compression on your middleware endpoints, and consider using more efficient serialization formats like Protocol Buffers for high-traffic applications.

Looking Forward

Mobile integration with Optimizely CMS will continue evolving. GraphQL is becoming the standard, and edge computing platforms like Vercel and Cloudflare Workers are making it easier to deploy fast, global middleware.

The key to success is starting simple and iterating. Get your basic middleware working securely, then add features like advanced caching, real-time updates, and performance optimizations based on actual usage patterns.

Your mobile users depend on fast, reliable access to your content. A well-designed middleware layer ensures they get exactly that, while keeping your CMS secure and your development team productive.

Start with the security fundamentals, add caching for performance, and always monitor your production deployment. With these pieces in place, you'll have a solid foundation for delivering great mobile experiences powered by Optimizely CMS.

Share this article

Ready to start
your project?

Our development team is ready to transform your vision into reality and bring your next innovation to life.