Static-Dynamic Separation Configuration

Static and dynamic requests have different performance and scaling characteristics, yet many web applications still deliver both through the same application server. Static-dynamic separation changes that model by routing cacheable assets such as HTML, CSS, JavaScript, images, fonts, and videos through a lightweight file server or CDN, while reserving application servers for personalized content, API calls, and database-backed operations.

This separation reduces repeated computation, shortens network latency, and allows static content to remain available even when the application tier is under heavy load.
A typical architecture includes three layers:

  • Edge or CDN layer: Caches frequently requested static files and serves them from locations close to users.
  • Reverse proxy layer: Uses Nginx or a similar service to distinguish static paths from dynamic routes.
  • Application layer: Runs the main application, executes business logic, and communicates with databases or external services.

For example, requests for /assets/app.a82f1c.js can be handled directly by Nginx, while requests for /api/orders are forwarded to an application running on port 3000. Because the two request types no longer compete for the same processing capacity, dynamic endpoints can respond more quickly during traffic spikes.

A useful design principle is to treat static content as immutable. If a file changes, its URL should change as well. This makes long-lived caching safe and removes the need to purge every affected browser or CDN cache manually.

Separating Static and Dynamic Requests with Nginx

The following configuration demonstrates the basic routing model:

server {
    listen 80;
    server_name www.example.com;

    # Fingerprinted build assets can be cached for one year.
    location /assets/ {
        alias /var/www/site/assets/;
        add_header Cache-Control "public, max-age=31536000, immutable";
        access_log off;
    }

    # Images not managed by the fingerprinted build pipeline.
    location /images/ {
        alias /var/www/site/images/;
        add_header Cache-Control "public, max-age=604800";
        access_log off;
    }

    # API requests are handled by the backend application.
    location /api/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_connect_timeout 5s;
        proxy_read_timeout 30s;
    }

    # All remaining requests, including application routes, go to the backend.
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

The /assets/ and /images/ locations serve files directly from the local filesystem. The /api/ location sends dynamic requests to the backend without unnecessary static-file processing. The final location / block supports application pages and client-side routes that must be rendered by the backend.

The immutable directive should only be used for content-addressed files. If app.js can change while retaining the same URL, aggressive long-term caching may prevent users from receiving the latest version.

Aligning the Frontend Build

Server routing and frontend deployment must use a consistent directory structure. Build tools such as Vite, webpack, and Rollup can place generated files in a dedicated output directory, while the deployment process copies that directory to the path referenced by Nginx.

Several practices improve the result:

  • Fingerprint assets: Generate names such as main.8c31d2.js or logo.4f9a10.png. A content change then produces a new URL and automatically bypasses existing caches.
  • Configure the public path: Ensure the build’s base or publicPath setting matches the static prefix exposed by the web server.
  • Cache HTML conservatively: HTML often points to the current asset versions and should usually receive a short cache lifetime or revalidation policy.
  • Preload critical files: Add preload hints for important fonts, stylesheets, or scripts to reduce discovery delays.
  • Avoid accidental cache conflicts: Do not assign the same long-lived cache policy to user-specific data, API responses, or HTML pages containing personalized information.

If assets are hosted on a separate domain, normal cross-origin rules still apply to fonts, fetched modules, images, and other programmatically loaded resources. Configure the appropriate CORS headers rather than broadly enabling unrestricted access.

Adding a CDN

Nginx can serve static files efficiently on its own, but a CDN extends that performance across regions. CDN edge nodes keep copies of popular assets near users, reducing origin traffic and shielding the web server from repeated requests.

A dedicated hostname such as static.example.com is often preferable to placing assets on the main application domain. It allows independent DNS, certificate, cache, and firewall policies. It can also prevent application cookies from being attached to every asset request.

When integrating a CDN:

  1. Upload or allow the CDN to fetch assets from the origin.
  2. Define cache rules for JavaScript, CSS, images, fonts, and video files.
  3. Use versioned filenames or cache tags for controlled invalidation.
  4. Configure HTTPS at both the edge and origin.
  5. Restrict origin access so traffic cannot bypass the CDN unintentionally.
  6. Exclude personalized responses, signed URLs, and administrative paths from shared caching.

Cache invalidation should normally be an automatic consequence of deployment rather than a manual operation. If a file’s content hash changes, its new URL naturally creates a cache miss. This model is safer than deleting objects from many edge locations after every release.

Compression and Transfer Optimization

Static separation also creates a clear boundary for transfer optimizations. Text-based formats such as HTML, CSS, JavaScript, JSON, and SVG benefit greatly from Brotli or gzip compression. Images should instead be optimized for the format in use, with modern formats such as WebP or AVIF where browser support permits.

Other useful measures include:

  • Using responsive image sizes to avoid downloading unnecessarily large files.
  • Setting precise cache lifetimes for different asset categories.
  • Enabling HTTP/2 or HTTP/3 between users and the CDN.
  • Reducing unused JavaScript and splitting large bundles into smaller chunks.
  • Applying preload hints only to resources needed during the initial rendering stage.

Monitoring the Architecture

The health of the separation model should be evaluated with both infrastructure and application metrics. Important signals include:

  • CDN and proxy cache hit ratio: A low rate may indicate incorrect cache headers, excessive purging, or cache keys that are too specific.
  • Static and dynamic latency: Compare edge delivery time, time to first byte, and total response time for each request class.
  • Origin traffic and bandwidth: A sudden increase may reveal cache misses, repeated deployments, or assets bypassing the CDN.
  • Application CPU and memory: These metrics should remain focused on business workloads rather than file serving.
  • Error rates: Track 4xx and 5xx responses separately for static and dynamic endpoints.
  • Stale-content incidents: Monitor deployments and cache behavior together to ensure asset versions remain synchronized.

Access logs can be disabled for high-volume static locations, as shown in the Nginx example, but sampling or dedicated CDN analytics may still be needed when troubleshooting cache issues. For dynamic requests, retaining the forwarded client and protocol headers ensures that applications can generate correct redirects, secure cookies, and audit logs.

Operational Considerations

Static-dynamic separation improves resilience, but it does not make dynamic HTML independent from the backend by itself. If the HTML document is generated dynamically and the application server is unavailable, users may still see an error page even though CSS and JavaScript remain reachable. Applications that require a complete fallback experience should consider deploying a static error shell or maintaining a last-known-good page.

Deployments should also be atomic. Upload the new fingerprinted assets before switching HTML references to them, verify that the CDN can retrieve every new file, and only then route application traffic to the new release. This order prevents pages from referencing assets that have not yet propagated to every edge location.

Finally, treat caching as part of application design rather than an afterthought. Define which resources are public, which are personalized, and how long each category may be stored. With clear routing, content-based versioning, and observable cache behavior, static-dynamic separation can deliver faster responses, lower infrastructure costs, and a more resilient web architecture.