How to Personalize Your Website by User Location (Free API + JavaScript)

# javascript# webdev# tutorial# api
How to Personalize Your Website by User Location (Free API + JavaScript)Ozor

Detect 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.

What We're Building

A lightweight location detection system that:

  • Detects the visitor's country, city, and timezone
  • Displays local currency symbols
  • Shows location-aware greetings
  • Works entirely client-side (no backend needed)

Step 1: Detect the Visitor's Location

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"
// }
Enter fullscreen mode Exit fullscreen mode

Get a free API key at frostbyte.world — 200 free requests, no credit card.

Step 2: Personalize the Greeting

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}`;
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Display Local Currency

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"
Enter fullscreen mode Exit fullscreen mode

Note: For accurate conversion rates, you'd combine this with a currency exchange API. This example shows the local symbol formatting.

Step 4: Put It All Together

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>
Enter fullscreen mode Exit fullscreen mode

Step 5: Country-Based Content Switching

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]);
  }
}
Enter fullscreen mode Exit fullscreen mode

Performance Tips

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;
}
Enter fullscreen mode Exit fullscreen mode

This makes the API call once per session instead of on every page load.

Full Working Example

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);
Enter fullscreen mode Exit fullscreen mode

Why Not Use the Browser's Geolocation API?

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.

Get Started

  1. Grab a free API key at frostbyte.world — 200 free requests, no card required
  2. Copy the code above into your project
  3. Replace YOUR_API_KEY with your actual key

The 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",...}
Enter fullscreen mode Exit fullscreen mode

Building something with location data? I'd love to hear about it in the comments.