ESI Support

Edge Side Includes for mixing cached and dynamic content.


Overview

Edge Side Includes (ESI) allows you to cache most of a page while keeping specific sections dynamic. This is perfect for pages that are mostly static but contain personalized fragments.

How It Works

  1. Your page includes ESI tags pointing to fragment URLs
  2. The main page is cached at the edge
  3. When served, ESI tags are processed and fragments are fetched
  4. Fragments can have their own cache rules
  5. Final page is assembled and returned to the visitor

ESI Tags

Basic Include

<esi:include src="/esi/user-greeting" />

This fetches /esi/user-greeting from your origin and inserts the response.

With Fallback

<esi:include src="/esi/user-greeting" onerror="continue" />

If the fragment fails to load, the page continues without it.


Example: Cached Page with Dynamic Header

<!DOCTYPE html>
<html>
<head>
    <title>My Page</title>
</head>
<body>
    <!-- Dynamic user greeting -->
    <esi:include src="/esi/user-header" />

    <!-- Cached main content -->
    <main>
        <h1>Welcome to our store</h1>
        <p>This content is cached.</p>
    </main>

    <!-- Dynamic cart widget -->
    <esi:include src="/esi/mini-cart" />
</body>
</html>

Fragment Endpoints

Your origin serves the fragments:

// /esi/user-header
Route::get('/esi/user-header', function() {
    if (auth()->check()) {
        return view('partials.user-greeting', ['user' => auth()->user()]);
    }
    return view('partials.guest-greeting');
});

// /esi/mini-cart
Route::get('/esi/mini-cart', function() {
    return view('partials.mini-cart', ['cart' => Cart::get()]);
});

Enabling ESI

Dashboard

  1. Go to your accelerator settings
  2. Enable ESI Processing
  3. Save changes

Fragment Caching

By default, fragments are NOT cached (fetched on every request). To cache fragments, send cache headers:

// Cache fragment for 5 minutes
return response()
    ->view('partials.user-greeting')
    ->header('Cache-Control', 'public, max-age=300');

Cache Rules

Parent Page

The main page can be cached normally. ESI tags are processed during delivery.

Cache-Control: public, max-age=3600

Fragment Pages

Each fragment can have its own cache rules:

Fragment Recommended TTL
User greeting No cache (dynamic)
Mini cart No cache (dynamic)
Recently viewed 5 minutes
Product recommendations 15 minutes
Footer links 1 hour

Performance Considerations

Fragment Latency

Each ESI include adds latency. Minimize fragments per page.

Good: 1-3 fragments per page Avoid: 10+ fragments per page

Parallel Fetching

NordicCDN fetches multiple fragments in parallel when possible:

<!-- These fetch in parallel -->
<esi:include src="/esi/header" />
<esi:include src="/esi/sidebar" />
<esi:include src="/esi/footer" />

Fragment Size

Keep fragments small. Large fragments negate caching benefits.


Session Handling

Cookie Forwarding

ESI fragments receive the visitor's cookies:

// Fragment can access session
Route::get('/esi/cart-count', function() {
    return response()->json([
        'count' => session('cart_count', 0)
    ]);
});

Authenticated Fragments

For user-specific fragments:

Route::get('/esi/account-menu', function() {
    if (!auth()->check()) {
        return '<a href="/login">Sign In</a>';
    }
    return view('partials.account-menu');
})->middleware('web'); // Include session middleware

WordPress ESI

For WordPress with ESI:

User-Specific Header

// functions.php
function esi_user_header() {
    if (is_user_logged_in()) {
        echo '<esi:include src="/wp-json/mysite/v1/user-header" />';
    } else {
        echo '<a href="/wp-login.php">Log In</a>';
    }
}

// REST endpoint
add_action('rest_api_init', function() {
    register_rest_route('mysite/v1', '/user-header', [
        'methods' => 'GET',
        'callback' => function() {
            $user = wp_get_current_user();
            return 'Welcome, ' . esc_html($user->display_name);
        },
        'permission_callback' => '__return_true'
    ]);
});

Troubleshooting

ESI Tags Appearing in Output

If you see raw <esi:include> tags:

  1. Verify ESI is enabled in accelerator settings
  2. Check the page is being served through the CDN
  3. Verify Content-Type is text/html

Fragments Not Loading

  1. Check fragment URL is accessible
  2. Verify fragment doesn't return errors
  3. Check fragment path is not in cache bypass list

Session Data Missing in Fragments

  1. Ensure cookies are being forwarded
  2. Verify session middleware is applied to fragment route
  3. Check that fragment URL is on the same domain

Best Practices

1. Use ESI Sparingly

ESI adds complexity. Use JavaScript for simple updates (cart count, notifications).

2. Cache What You Can

Even personalized content often has cacheable patterns:

// Cache by user role, not by user
Route::get('/esi/admin-menu', function() {
    $role = auth()->user()?->role ?? 'guest';
    return response()
        ->view("partials.admin-menu.{$role}")
        ->header('Cache-Control', 'private, max-age=300');
});

3. Provide Fallbacks

Always use onerror="continue" for non-critical fragments:

<esi:include src="/esi/recommendations" onerror="continue" />

4. Monitor Fragment Performance

Track fragment response times separately from main pages.