Geo Redirect
January 21, 2026 ยท View on GitHub
Redirects users to country-specific sites based on their IP geolocation. Useful for serving localized content or complying with regional regulations.
Source Code
const std = @import("std");
const zigly = @import("zigly");
const geo = zigly.geo;
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
var downstream = try zigly.downstream();
// Get client IP and look up location
const client_ip = try zigly.http.Downstream.getClientIpAddr();
var buf: [4096]u8 = undefined;
const result = geo.lookup(allocator, client_ip, &buf) catch {
// If geo lookup fails, proceed to default
try downstream.proxy("origin", null);
return;
};
const country = result.value.country_code;
// Get current path
var uri_buf: [4096]u8 = undefined;
const path = try downstream.request.getPath(&uri_buf);
// Skip redirect for certain paths
if (std.mem.startsWith(u8, path, "/api/") or
std.mem.startsWith(u8, path, "/static/"))
{
try downstream.proxy("origin", null);
return;
}
// Redirect based on country
var redirect_buf: [256]u8 = undefined;
if (std.mem.eql(u8, country, "DE") or
std.mem.eql(u8, country, "AT") or
std.mem.eql(u8, country, "CH"))
{
const redirect = try std.fmt.bufPrint(&redirect_buf, "https://de.example.com{s}", .{path});
try downstream.redirect(302, redirect);
} else if (std.mem.eql(u8, country, "FR") or
std.mem.eql(u8, country, "BE"))
{
const redirect = try std.fmt.bufPrint(&redirect_buf, "https://fr.example.com{s}", .{path});
try downstream.redirect(302, redirect);
} else if (std.mem.eql(u8, country, "JP")) {
const redirect = try std.fmt.bufPrint(&redirect_buf, "https://jp.example.com{s}", .{path});
try downstream.redirect(302, redirect);
} else {
// Default: proxy to origin without redirect
try downstream.proxy("origin", null);
}
}
How It Works
- Get the client IP address
- Look up geolocation data for that IP using
geo.lookup() - Extract the country code from the result
- Use
getPath()to extract the request path - Skip redirects for API and static asset paths
- Redirect to the appropriate regional domain based on country
- Fall back to the default origin for unmatched countries
The downstream.redirect(302, url) method sends an HTTP 302 redirect response.
Geolocation Data
The geo.lookup() function returns rich location data:
const result = geo.lookup(allocator, ip, &buf) catch |err| {
// Handle lookup failure
};
const data = result.value;
// Available fields:
// data.country_code - "US", "DE", "JP", etc.
// data.country_name - "United States", "Germany", etc.
// data.region - State/province
// data.city - City name
// data.postal_code - ZIP/postal code
// data.latitude - GPS latitude
// data.longitude - GPS longitude
// data.utc_offset - Timezone offset
// data.continent - "NA", "EU", "AS", etc.
Testing
With local emulators, localhost IPs won't have geolocation data unless configured, so the request falls through to the default origin. In production, real client IPs will have geolocation data.
curl -v http://127.0.0.1:7676/products
# Falls through to origin since localhost has no geo data
curl -v http://127.0.0.1:7676/api/data
# Skips redirect, proxies to origin
Variations
Continent-based routing:
if (std.mem.eql(u8, data.continent, "EU")) {
try downstream.proxy("eu_origin", null);
} else if (std.mem.eql(u8, data.continent, "AS")) {
try downstream.proxy("asia_origin", null);
} else {
try downstream.proxy("us_origin", null);
}
City-level targeting:
if (std.mem.eql(u8, data.city, "San Francisco")) {
try downstream.response.headers.set("X-Promo", "sf-special");
}
GDPR compliance:
const eu_countries = [_][]const u8{ "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE" };
for (eu_countries) |eu| {
if (std.mem.eql(u8, country, eu)) {
try downstream.response.headers.set("X-GDPR-Region", "true");
break;
}
}
Cookie-based override:
var cookie_buf: [256]u8 = undefined;
if (downstream.request.headers.get("Cookie", &cookie_buf)) |cookies| {
if (std.mem.indexOf(u8, cookies, "region=us")) |_| {
// User chose US region, respect their preference
try downstream.proxy("us_origin", null);
return;
}
}
// Otherwise use geo-based routing