All Request Headers Available in AWS Amplify Hosting (Geo / ASN / Device)
TL;DR
The request handlers in AWS Amplify Hosting's SSR receive a wealth of user information that CloudFront adds to every incoming request.
Without integrating MaxMind, geoip-lite, or any other GeoIP library, you can get the country, region, city, postal code, latitude/longitude, ASN, and device type out of the box.
All the headers that actually arrive
I dumped the full contents of request.headers in a production deployment and confirmed the following:
Geo (location) headers
| Header | Example value | Meaning |
|---|---|---|
cloudfront-viewer-country |
JP |
ISO 3166-1 alpha-2 country code |
cloudfront-viewer-country-name |
Japan |
Country name (English) |
cloudfront-viewer-country-region |
13 |
ISO 3166-2 subdivision code (Tokyo) |
cloudfront-viewer-country-region-name |
Tokyo |
Region / state name |
cloudfront-viewer-city |
Matsubara |
City name |
cloudfront-viewer-postal-code |
156-0041 |
Postal code |
cloudfront-viewer-latitude |
35.xxxxxx |
Latitude |
cloudfront-viewer-longitude |
139.xxxxxx |
Longitude |
cloudfront-viewer-time-zone |
Asia/Tokyo |
Time zone |
You even get the postal code and latitude / longitude. Treat these with care from a privacy perspective.
The city name was not actually the city I was accessing from — it was off by about 3 km.
Network headers
| Header | Example value | Meaning |
|---|---|---|
cloudfront-viewer-asn |
25XX |
AS number |
cloudfront-viewer-address |
203.0.xxx.xxx:53xxx |
Client IP and port |
cloudfront-viewer-tls |
TLSv1.3:TLS_AES_128_GCM_SHA256:connectionReused |
TLS version and cipher suite |
cloudfront-viewer-http-version |
3.0 |
HTTP version |
cloudfront-forwarded-proto |
https |
Client-side protocol |
x-forwarded-for |
203.0.xxx.xxx, 64.252.xxx.xxx |
List of IPs along the proxy path |
x-forwarded-host |
example.com |
Client-side Host |
x-forwarded-port |
443 |
Client-side port |
x-forwarded-proto |
https |
Client-side protocol |
via |
3.0 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.cloudfront.net (CloudFront) |
CloudFront pass-through information |
Some of the values above have been partially anonymized.
Getting the ASN is a nice bonus. With it, you can classify access like "came from a specific ISP" or "came from an AWS data center."
Device type
| Header | Example value |
|---|---|
cloudfront-is-desktop-viewer |
true |
cloudfront-is-mobile-viewer |
false |
cloudfront-is-tablet-viewer |
false |
cloudfront-is-smarttv-viewer |
false |
cloudfront-is-ios-viewer |
false |
cloudfront-is-android-viewer |
false |
You can distinguish desktop / mobile / tablet / TV without parsing the User-Agent yourself — CloudFront looks at the UA and gives you the result.
AWS internal trace headers
| Header | Purpose |
|---|---|
x-amzn-trace-id |
Trace ID for X-Ray and similar |
x-amz-cf-id |
CloudFront request ID (needed when contacting AWS support) |
Original browser headers (not overwritten!)
| Header | Example value |
|---|---|
user-agent |
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ... Chrome/146.0.0.0 ... |
accept-language |
ja,en-US;q=0.9,en;q=0.8 |
host |
example.com |
Back around 2022 there was a report on GitHub issue (#2161) that "Amplify SSR replaces user-agent with Amazon CloudFront", but today the original browser UA is preserved. You can extract details with libraries like ua-parser-js.
How to read them in SvelteKit SSR
The request: Request object passed into load functions and actions handlers exposes .headers — just read them from there.
Example: load function
// src/routes/+page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ request }) => {
const headers = request.headers;
const country = headers.get('cloudfront-viewer-country-name'); // 'Japan'
const region = headers.get('cloudfront-viewer-country-region-name'); // 'Tokyo'
const asn = headers.get('cloudfront-viewer-asn'); // '25XX'
return {
location: { country, region, asn }
};
};
Example: action handler
// src/routes/+page.server.ts
import type { Actions } from './$types';
export const actions: Actions = {
default: async ({ request }) => {
const country = request.headers.get('cloudfront-viewer-country');
// ...
}
};
Example: API endpoint (+server.ts)
// src/routes/api/some/+server.ts
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ request }) => {
const country = request.headers.get('cloudfront-viewer-country');
return new Response(JSON.stringify({ country }));
};
Wrap it up as a utility
In a real project, it's convenient to extract this into a utility function.
// src/lib/server/geo.ts
export type DeviceType = 'desktop' | 'mobile' | 'tablet' | 'tv' | undefined;
export interface GeoInfo {
country?: string;
countryCode?: string;
region?: string;
asn?: number;
deviceType?: DeviceType;
}
export function extractGeoInfo(headers: Headers): GeoInfo {
const asnRaw = headers.get('cloudfront-viewer-asn');
const asn = asnRaw ? parseInt(asnRaw, 10) : undefined;
let deviceType: DeviceType;
if (headers.get('cloudfront-is-tablet-viewer') === 'true') {
deviceType = 'tablet';
} else if (headers.get('cloudfront-is-mobile-viewer') === 'true') {
deviceType = 'mobile';
} else if (headers.get('cloudfront-is-smarttv-viewer') === 'true') {
deviceType = 'tv';
} else if (headers.get('cloudfront-is-desktop-viewer') === 'true') {
deviceType = 'desktop';
}
return {
country: headers.get('cloudfront-viewer-country-name') || undefined,
countryCode: headers.get('cloudfront-viewer-country') || undefined,
region: headers.get('cloudfront-viewer-country-region-name') || undefined,
asn: asn !== undefined && Number.isFinite(asn) ? asn : undefined,
deviceType
};
}
Caller side:
import { extractGeoInfo } from '$lib/server/geo';
export const load: PageServerLoad = async ({ request }) => {
const geo = extractGeoInfo(request.headers);
console.log(geo);
// { country: 'Japan', countryCode: 'JP', region: 'Tokyo', asn: 25XX, deviceType: 'desktop' }
};
References
We look forward to discussing your development needs.