Front-End Web & Mobile
Amplify Hosting Announces Skew Protection Support
A common challenge in web application development and deployment is version skew between client and server resources. Today, we’re excited to announce deployment skew protection for applications deployed to AWS Amplify Hosting. This feature helps verifies end users have a seamless experience during application deployments.
The Challenge
Modern web applications are complex systems comprising numerous static assets and server-side components that must all work together. In a world where its common to have multiple deployments occur hourly, version compatibility becomes a critical concern. When new deployments occur, users with cached versions of your application may attempt to fetch resources from the updated deployment, potentially resulting in 404 errors and broken functionality.
This challenge is compounded by client-server version skew, which manifests in two common scenarios. First, users often keep browser tabs open for extended periods, continuing to run older versions of your application while attempting to interact with updated backend services. Second, in mobile applications, users with disabled auto-updates may continue using outdated versions indefinitely, requiring backend services to maintain compatibility with multiple client versions simultaneously.
These version management challenges can significantly impact user experience and application reliability if not properly addressed. Consider the scenario:
- A user loads your application (version A)
- You deploy a new version (version B)
- The user’s cached JavaScript tries to load assets that only existed in version A
- Result: broken functionality and poor user experience
How skew protection works
Amplify Hosting now intelligently coordinates request resolution across multiple client sessions, ensuring precise routing to intended deployment versions.
1. Smart asset routing
When a request comes in, Amplify Hosting now:
- Identifies the deployment version that originated the request
- Routes and resolves the request to the identified version of the asset
- A hard refresh will always serve the latest deployment assets in a user session
2. Consistent version serving
The system confirms that:
- All assets from a single user session come from the same deployment
- New user sessions always get the latest version
- Existing sessions continue working with their original version until refresh
Advantages to skew protection
Here are some of the advantages of having skew protection:
- Zero Configuration: Works out of the box with popular frameworks
- Reliable Deployments: Eliminate 404 errors due to deployment skew
- Performance Optimized: Minimal impact on response times
Best practices
While skew protection handles most scenarios automatically, we recommend:
- Using atomic deployments
- Testing your deployment process in a staging environment
Enabling Skew Protection
Skew protection must be enabled for each Amplify app. This is done at the branch level for every Amplify Hosting app. You can read the full Amplify Hosting documentation here.
1. To enable a branch, click on App Settings, then click Branch Settings. Next, select the branch you want to enable followed by the Actions tab.
FIGURE 1 – Amplify Hosting Branch Settings
2. For skew protection to take effect, you must deploy the application once.
Note: Skew protection will not be available to customers using our legacy SSRv1/WEB_DYNAMIC applications.
Pricing: There is no additional cost for this feature and it is available to all Amplify Hosting regions.
Tutorial
To get started, follow these steps to create a Next.js application and enable skew protection on it.
Prerequisites
Before you begin, make sure you have the following installed:
- Node.js (v18.x or later)
- npm or npx (v10.x or later)
- Git (v2.39.5 or later)
Create a Next.js app
Let’s create a Next.js app to see skew protection in action.
- Create a new Next.js 15 app with Typescript and Tailwind CSS
$ npx create-next-app@latest skew-protection-demo --typescript --tailwind --eslint
$ cd skew-protection-demo
2. Create a SkewProtectionDemo component that lists fingerprinted assets with deployment IDs. Use the following code to create the component.
Note: Amplify will automatically tag the fingerprinted assets of a NextJS app with a dpl
query parameter set to a UUID. This UUID is also available during the build via the AWS_AMPLIFY_DEPLOYMENT_ID
environment variable for other frameworks.
// app/components/SkewProtectionDemo.tsx
"use client";
import { useState, useEffect } from "react";
import DeploymentTester from "./DeploymentTester";
interface Asset {
type: string;
url: string;
dpl: string;
}
export default function SkewProtectionDemo() {
const [fingerprintedAssets, setFingerprintedAssets] = useState<Asset[]>([]);
const [deploymentId, setDeploymentId] = useState<string>("Unknown");
// Detect all assets with dpl parameters on initial load
useEffect(() => {
detectFingerprintedAssets();
}, []);
// Function to detect all assets with dpl parameters
const detectFingerprintedAssets = () => {
// Find all assets with dpl parameter (CSS and JS)
const allElements = [
...Array.from(document.querySelectorAll('link[rel="stylesheet"]')),
...Array.from(document.querySelectorAll("script[src]"))
];
const assets = allElements
.map(element => {
const url = element.getAttribute(element.tagName.toLowerCase() === "link" ? "href" : "src");
if (!url || !url.includes("?dpl=")) return null;
// Extract the dpl parameter
let dplParam = "unknown";
try {
const urlObj = new URL(url, window.location.origin);
dplParam = urlObj.searchParams.get("dpl") || "unknown";
} catch (e) {
console.error("Error parsing URL:", e);
}
return {
type: element.tagName.toLowerCase() === "link" ? "css" : "js",
url: url,
dpl: dplParam,
};
})
.filter(asset => asset !== null);
setFingerprintedAssets(assets);
// Set deployment ID if assets were found
if (assets.length > 0) {
setDeploymentId(assets[0]?.dpl || "Unknown");
}
};
// Function to format URL to highlight the dpl parameter
const formatUrl = (url: string) => {
if (!url.includes("?dpl=")) return url;
const [baseUrl, params] = url.split("?");
const dplParam = params.split("&").find(p => p.startsWith("dpl="));
if (!dplParam) return url;
const otherParams = params.split("&").filter(p => !p.startsWith("dpl=")).join("&");
return (
<>
<span className="text-gray-400">{baseUrl}?</span>
{otherParams && <span className="text-gray-400">{otherParams}&</span>}
<span className="text-yellow-300 font-bold">{dplParam}</span>
</>
);
};
return (
<main className="min-h-screen p-6 bg-white">
<div className="w-full max-w-2xl mx-auto">
<h1 className="text-2xl font-bold text-gray-900 mb-6">
Amplify Skew Protection Demo
</h1>
<div className="grid grid-cols-1 gap-4 mb-6">
<div className="flex items-center p-4 bg-white text-gray-800 rounded-md border border-gray-200 hover:bg-gray-50 transition-colors">
<div className="w-10 h-10 flex items-center justify-center bg-gray-100 rounded-lg mr-3">
<span className="text-lg">🚀</span>
</div>
<div>
<p className="font-medium">Zero-Downtime Deployments</p>
<p className="text-xs text-gray-600">Assets and API routes remain accessible during deployments using deployment ID-based routing</p>
</div>
</div>
<div className="flex items-center p-4 bg-white text-gray-800 rounded-md border border-gray-200 hover:bg-gray-50 transition-colors">
<div className="w-10 h-10 flex items-center justify-center bg-gray-100 rounded-lg mr-3">
<span className="text-lg">⚡️</span>
</div>
<div>
<p className="font-medium">Built-in Next.js Support</p>
<p className="text-xs text-gray-600">Automatic asset fingerprinting and deployment ID injection for Next.js applications</p>
</div>
</div>
<div className="flex items-center p-4 bg-white text-gray-800 rounded-md border border-gray-200 hover:bg-gray-50 transition-colors">
<div className="w-10 h-10 flex items-center justify-center bg-gray-100 rounded-lg mr-3">
<span className="text-lg">🔒</span>
</div>
<div>
<p className="font-medium">Advanced Security</p>
<p className="text-xs text-gray-600">Protect against compromised builds by calling the delete-job API to remove affected deployments</p>
</div>
</div>
</div>
<div className="bg-gradient-to-r from-blue-900 to-purple-900 p-4 rounded-md mb-6">
<h2 className="text-sm font-medium text-blue-200 mb-1">
Current Deployment ID
</h2>
<div className="p-2 bg-black bg-opacity-30 rounded-md font-mono text-lg text-center text-yellow-300">
{deploymentId}
</div>
</div>
{fingerprintedAssets.length > 0 ? (
<>
<h2 className="text-xl font-bold text-gray-900 mb-6">
Fingerprinted Assets
</h2>
<div className="border border-gray-200 rounded-md overflow-hidden mb-6 bg-white">
<div className="max-h-48 overflow-y-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-900 uppercase tracking-wider w-16">
Type
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-900 uppercase tracking-wider">
URL
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{fingerprintedAssets.map((asset, index) => (
<tr key={index} className="bg-white hover:bg-gray-50">
<td className="px-3 py-2 text-sm text-gray-900">
<span
className={`inline-block px-2 py-0.5 rounded-full text-xs ${
asset.type === "css"
? "bg-blue-100 text-blue-800"
: "bg-yellow-100 text-yellow-800"
}`}
>
{asset.type}
</span>
</td>
<td className="px-3 py-2 text-xs font-mono break-all text-gray-900">
{formatUrl(asset.url)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<h2 className="text-xl font-bold text-gray-900 mb-6">
Test Deployment Routing
</h2>
<DeploymentTester />
</>
) : (
<div className="p-6 text-center text-gray-600 border border-gray-200 rounded-md">
<p>No fingerprinted assets detected</p>
<p className="text-sm mt-2">
Deploy to Amplify Hosting to see skew protection in action
</p>
</div>
)}
</div>
</main>
);
}
3. Next, create a DeploymentTester component that demonstrates how API requests maintain deployment consistency by sending the X-Amplify-Dpl
header with each request, allowing Amplify to route to the correct API version. Use the following code to create the component.
// app/components/DeploymentTester.tsx
'use client';
import { useState } from 'react';
interface ApiResponse {
message: string;
timestamp: string;
version: string;
deploymentId: string;
}
export default function DeploymentTester() {
const [testInProgress, setTestInProgress] = useState(false);
const [testOutput, setTestOutput] = useState<ApiResponse | null>(null);
const [callCount, setCallCount] = useState(0);
const runApiTest = async () => {
setTestInProgress(true);
setCallCount(prev => prev + 1);
try {
const response = await fetch('/api/skew-protection', {
headers: {
// Amplify provides the deployment ID as an environment variable during build time
'X-Amplify-Dpl': process.env.AWS_AMPLIFY_DEPLOYMENT_ID || '',
}
});
if (!response.ok) {
throw new Error(`API returned ${response.status}`);
}
const data = await response.json();
setTestOutput(data);
} catch (error) {
console.error("API call failed", error);
setTestOutput({
message: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
timestamp: new Date().toISOString(),
version: 'error',
deploymentId: 'error'
});
} finally {
setTestInProgress(false);
}
};
return (
<div className="border border-gray-200 rounded-md overflow-hidden bg-white">
<div className="bg-gray-50 px-4 py-3 flex justify-between items-center border-b border-gray-200">
<span className="font-medium text-gray-800">Test Deployment Routing</span>
<button
onClick={runApiTest}
disabled={testInProgress}
className={`px-3 py-1 rounded text-sm ${
testInProgress
? 'bg-gray-200 text-gray-500 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700 text-white'
}`}
>
{testInProgress ? "Testing..." : "Test Route"}
</button>
</div>
<div className="p-4">
{testOutput ? (
<div className="p-3 bg-gray-50 rounded border border-gray-200 font-mono text-sm">
<div className="text-green-600 mb-2">{testOutput.message}</div>
<div className="text-gray-600 text-xs space-y-1">
<div>API Version: <span className="text-blue-600 font-medium">{testOutput.version}</span></div>
<div>Deployment ID: <span className="text-purple-600 font-medium">{testOutput.deploymentId}</span></div>
<div>Call #: {callCount}</div>
<div>Time: {new Date(testOutput.timestamp).toLocaleTimeString()}</div>
</div>
</div>
) : testInProgress ? (
<div className="p-3 bg-gray-50 rounded border border-gray-200 text-sm text-gray-600">
Testing deployment routing...
</div>
) : (
<div className="p-3 bg-gray-50 rounded border border-gray-200 text-sm text-gray-600">
Click "Test Route" to verify how requests are routed to the correct deployment version
</div>
)}
</div>
</div>
);
}
4. Now create an API route that uses the X-Amplify-Dpl
header to identify which deployment the request is coming from, simulating how Amplify routes API requests to maintain version consistency during deployments. Use the following code to create the API route:
// app/api/skew-protection/route.ts
import { NextResponse } from 'next/server';
import { type NextRequest } from 'next/server';
// This version identifier can be changed between deployments to demonstrate skew protection
const CURRENT_API_VERSION = "v2.0";
export async function GET(request: NextRequest) {
// Get the deployment ID from the X-Amplify-Dpl header
// This is how Amplify routes API requests to the correct deployment version
const deploymentId = request.headers.get('x-amplify-dpl') || '';
// Determine which version to serve based on deployment ID
const apiVersion = CURRENT_API_VERSION;
const message = `Hello from API ${apiVersion}! 🚀`;
// Return the response with deployment information
return NextResponse.json({
message,
version: apiVersion,
deploymentId: deploymentId || 'none',
timestamp: new Date().toISOString()
});
}
5. Add the Amplify deployment ID environment variable to make it accessible to client code
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
env: {
AWS_AMPLIFY_DEPLOYMENT_ID: process.env.AWS_AMPLIFY_DEPLOYMENT_ID || '',
}
};
export default nextConfig;
6. Push the changes to a GitHub repository
- Create a new GitHub repository
- Add and commit the changes to the Git branch
- Add remote origin and push the changes upstream
git add .
git commit -m "initial commit"
git remote add origin http://github.com/<OWNER>/amplify-skew-protection-demo.git
git push -u origin main
Deploy the application to Amplify Hosting
Use the following steps to deploy your newly constructed application to AWS Amplify Hosting:
- Sign in to the AWS Amplify console.
- Choose Create new app and select GitHub as the repository source
- Authorize Amplify to access your GitHub account
- Choose the repository and branch you created
- Review the App settings and then choose Next
- Review the overall settings and choose Save and deploy
Enable skew protection
In the Amplify console, navigate to App settings and then Branch settings. Select the Branch, and from the Actions dropdown menu choose Enable skew protection.
FIGURE 2 – Amplify Hosting Branch Settings
Next, navigate to the deployments page and redeploy your application. When skew protection is enabled for an application, AWS Amplify must update its CDN cache configuration. Therefore, you should expect your first deployment after enabling skew protection to take up to ten minutes.
FIGURE 3 – Amplify Hosting App Deployments
Access the deployed Next.js app
Navigate to the Overview tab in the Amplify console and open the default Amplify generated URL in the browser. You should now observe a list of fingerprinted assets for your app along with the deployment ID.
FIGURE 4 – Amplify Hosting App Settings – Branch level
FIGURE 5 – Demo App Homepage
Testing skew protection
When you deploy your Next.js application to Amplify, each deployment gets assigned a unique deployment ID. This ID is automatically injected into your static assets (JS, CSS) and API routes to ensure version consistency. Let’s see it in action:
- Asset Fingerprinting: Notice how each static asset URL includes a ?dpl= parameter with your current deployment ID. This ensures browsers always fetch the correct version of your assets.
- API Routing: The
Test Route
button demonstrates how Amplify routes API requests. When clicked, it makes a request to the/api/skew-protection
endpoint. Since the request utilizes theX-Amplify-Dpl
header to match your current deployment ID, it ensures routing to the correct API version.
This means that even during deployments, your users won’t experience version mismatches and each user’s session stays consistent with the version they initially loaded, preventing bugs that could occur when client and server versions don’t match.
Try it yourself
- Keep your current browser tab open and click Test Route to see the API version and deployment ID match.
- Deploy a new version with a different CURRENT_API_VERSION in
api/skew-protection/route.ts
- Open your application in a new incognito window
- Compare the behavior:
-
- Your original tab will maintain the old version
- The new incognito window will show the new version
- Each tab’s assets and API calls will consistently match their respective versions
- Try clicking
Test Route
repeatedly in both windows – each will consistently route to its respective version, demonstrating how Amplify maintains session consistency even when multiple versions are live
-
- Compare the behavior:
FIGURE 6 -Side by Side Comparison of Skew Protection Behavior
This demonstrates how Amplify maintains version consistency for each user session, even when multiple versions of your application are running during deployments.
Congratulations, you’ve successfully created and verified skew protection on your Next.js application deployments on Amplify Hosting.
Cleanup
Delete the AWS Amplify app by navigating to App settings, next go to General settings, then choose Delete app.
Next Steps
- Enable Skew Protection for your application.
- Read our documentation to learn more about this feature!
About the Authors
Matt Auerbach, Senior Product Manager, Amplify Hosting
Matt Auerbach is a NYC-based Product Manager on the AWS Amplify Team. He educates developers regarding products and offerings, and acts as the primary point of contact for assistance and feedback. Matt is a mild-mannered programmer who enjoys using technology to solve problems and making people’s lives easier. B night, however…well he does pretty much the same thing. You can find Matt on X @mauerbac. He previously worked at Twitch, Optimizely and Twilio.
Jay Raval, Solutions Architect, Amplify Hosting
Jay Raval is a Solutions Architect on the AWS Amplify team. He’s passionate about solving complex customer problems in the front-end, web and mobile domain and addresses real-world architecture problems for development using front-end technologies and AWS. In his free time, he enjoys traveling and sports. You can find Jay on X @_Jay_Raval_