OzorDetect visitor country, city, and timezone with a free IP geolocation API. Personalize content, currency, and language automatically.
Every major website personalizes content by location — Amazon shows local currency, Netflix suggests regional content, news sites display local weather. You can do the same thing with a few lines of JavaScript and a free API.
In this tutorial, I'll show you how to detect your visitor's location and use it to personalize your website — no signup required.
A lightweight location detection system that:
First, let's get the visitor's IP and location data:
async function getVisitorLocation() {
// Get the visitor's IP
const ipRes = await fetch('https://api.frostbyte.world/ip/json');
const { ip } = await ipRes.json();
// Get geolocation data for that IP
const geoRes = await fetch(`https://api.frostbyte.world/api/geo/${ip}`, {
headers: { 'X-Api-Key': 'YOUR_API_KEY' }
});
return await geoRes.json();
}
// Example response:
// {
// "ip": "203.0.113.42",
// "country": "Australia",
// "country_code": "AU",
// "region": "New South Wales",
// "city": "Sydney",
// "latitude": -33.8688,
// "longitude": 151.2093,
// "timezone": "Australia/Sydney",
// "isp": "Telstra"
// }
Get a free API key at frostbyte.world — 200 free requests, no credit card.
Show a localized greeting based on timezone and country:
function getLocalGreeting(timezone, country) {
const hour = new Date(
new Date().toLocaleString('en-US', { timeZone: timezone })
).getHours();
let timeGreeting;
if (hour < 12) timeGreeting = 'Good morning';
else if (hour < 18) timeGreeting = 'Good afternoon';
else timeGreeting = 'Good evening';
// Add country-specific flavor
const greetings = {
JP: 'こんにちは! ',
FR: 'Bonjour! ',
DE: 'Hallo! ',
ES: 'Hola! ',
BR: 'Olá! ',
KR: '안녕하세요! ',
IT: 'Ciao! ',
};
const localHello = greetings[country] || '';
return `${localHello}${timeGreeting}`;
}
Show prices in the visitor's local currency:
const currencyMap = {
US: { code: 'USD', symbol: '$' },
GB: { code: 'GBP', symbol: '£' },
EU: { code: 'EUR', symbol: '€' },
JP: { code: 'JPY', symbol: '¥' },
AU: { code: 'AUD', symbol: 'A$' },
CA: { code: 'CAD', symbol: 'C$' },
IN: { code: 'INR', symbol: '₹' },
BR: { code: 'BRL', symbol: 'R$' },
// Eurozone countries
DE: { code: 'EUR', symbol: '€' },
FR: { code: 'EUR', symbol: '€' },
IT: { code: 'EUR', symbol: '€' },
ES: { code: 'EUR', symbol: '€' },
NL: { code: 'EUR', symbol: '€' },
};
function formatPrice(usdAmount, countryCode) {
const currency = currencyMap[countryCode] || { code: 'USD', symbol: '$' };
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency.code,
}).format(usdAmount);
}
// formatPrice(9.99, 'JP') → "¥9.99"
// formatPrice(9.99, 'GB') → "£9.99"
Note: For accurate conversion rates, you'd combine this with a currency exchange API. This example shows the local symbol formatting.
Here's a complete, copy-paste-ready implementation:
<div id="personalized-banner">
<p id="greeting">Welcome!</p>
<p id="location-info"></p>
<p id="local-time"></p>
</div>
<script>
async function personalize() {
try {
// 1. Get visitor IP
const ipRes = await fetch('https://api.frostbyte.world/ip/json');
const { ip } = await ipRes.json();
// 2. Get location data
const geoRes = await fetch(
`https://api.frostbyte.world/api/geo/${ip}`,
{ headers: { 'X-Api-Key': 'YOUR_API_KEY' } }
);
const geo = await geoRes.json();
// 3. Update the page
const hour = new Date(
new Date().toLocaleString('en-US', { timeZone: geo.timezone })
).getHours();
const timeGreeting = hour < 12
? 'Good morning'
: hour < 18 ? 'Good afternoon' : 'Good evening';
document.getElementById('greeting').textContent =
`${timeGreeting} from ${geo.city}!`;
document.getElementById('location-info').textContent =
`${geo.city}, ${geo.region} · ${geo.country}`;
document.getElementById('local-time').textContent =
`Local time: ${new Date().toLocaleString('en-US', {
timeZone: geo.timezone,
hour: '2-digit',
minute: '2-digit',
})}`;
} catch (err) {
console.log('Personalization unavailable:', err.message);
// Graceful fallback — page works fine without it
}
}
personalize();
</script>
Show different content based on location — useful for compliance banners, regional offers, or language switching:
async function showRegionalContent(geo) {
const { country_code } = geo;
// GDPR cookie banner for EU visitors
const euCountries = [
'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'
];
if (euCountries.includes(country_code)) {
showCookieBanner();
}
// Regional pricing page
if (['IN', 'BR', 'PH', 'NG'].includes(country_code)) {
showDiscountedPricing(); // PPP pricing for developing markets
}
// Language redirect suggestion
const langMap = { FR: '/fr', DE: '/de', ES: '/es', JP: '/ja' };
if (langMap[country_code]) {
suggestLanguageSwitch(langMap[country_code]);
}
}
Don't let personalization slow down your page:
// Cache the result in sessionStorage
async function getCachedLocation() {
const cached = sessionStorage.getItem('visitor_geo');
if (cached) return JSON.parse(cached);
const geo = await getVisitorLocation();
sessionStorage.setItem('visitor_geo', JSON.stringify(geo));
return geo;
}
This makes the API call once per session instead of on every page load.
Here's a Node.js/Express version for server-side personalization:
import express from 'express';
const app = express();
const API_KEY = process.env.FROSTBYTE_KEY;
app.get('/', async (req, res) => {
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
const geoRes = await fetch(
`https://api.frostbyte.world/api/geo/${ip}`,
{ headers: { 'X-Api-Key': API_KEY } }
);
const geo = await geoRes.json();
res.render('home', {
greeting: `Welcome from ${geo.city}, ${geo.country}!`,
timezone: geo.timezone,
showGdpr: isEU(geo.country_code),
});
});
app.listen(3000);
The browser's navigator.geolocation requires a permission popup and user consent. IP geolocation:
| Feature | Browser Geolocation | IP Geolocation |
|---|---|---|
| User permission required | Yes (popup) | No |
| Works without interaction | No | Yes |
| Accuracy | ~10m (GPS) | ~City level |
| Works server-side | No | Yes |
| Privacy-friendly | Requires consent | Uses public IP only |
For personalization (currency, language, content), city-level accuracy is enough. Save the GPS popup for maps.
YOUR_API_KEY with your actual keyThe IP detection endpoint (/ip/json) doesn't require authentication, so you can start testing immediately:
curl https://api.frostbyte.world/ip/json
# {"ip":"203.0.113.42"}
curl -H "X-Api-Key: YOUR_KEY" https://api.frostbyte.world/api/geo/203.0.113.42
# {"ip":"203.0.113.42","country":"Australia","city":"Sydney",...}
Building something with location data? I'd love to hear about it in the comments.