I wanted to compare South Korea and Japan's economies for a research project. Spent 30 minutes clicking around the World Bank website, trying to export data, struggling with their clunky interface.
So I built my own tool. In one day.
What I Built
CountryCompare — a free, interactive dashboard that lets you compare economic indicators between countries using World Bank data.
Try it: country-compare.com
What You Can Do
- Compare 2 countries side by side (free) or up to 10 (Pro)
- View 50 economic indicators across 6 categories: Economy, Labor, Society, Energy, Environment, Trade
- See 25 years of historical data in interactive charts
- Download PDF reports and CSV data
- Browse 50 country profiles with key economic snapshots
- Dark mode included
The Numbers
- 1,290 auto-generated pages (every country pair gets its own SEO-optimized page)
- 50 indicators from World Bank Open Data
- 50 country profiles
- $0 hosting cost (Vercel free tier)
- $0 data cost (World Bank API is free, no key required)
Tech Stack
- Next.js 14 (App Router, TypeScript)
- Chart.js + react-chartjs-2 for interactive charts
- Tailwind CSS for styling + dark mode
- Vercel for hosting (free tier)
- World Bank API for data (free, no API key needed)
How the World Bank API Works
The World Bank API is surprisingly simple and completely free:
GET https://api.worldbank.org/v2/country/US;JP/indicator/NY.GDP.MKTP.CD?format=json&date=2000:2024&per_page=500
Breaking it down:
-
/country/US;JP— semicolon-separated ISO2 codes for multiple countries -
/indicator/NY.GDP.MKTP.CD— the indicator code (this one is GDP in USD) -
format=json— returns JSON instead of XML -
date=2000:2024— 25 years of data in one call
Here's the actual fetch function from my codebase:
const BASE_URL = 'https://api.worldbank.org/v2';
export async function fetchIndicator(
countryCodes: string[],
indicatorId: string
) {
const codes = countryCodes.join(';');
const url = `${BASE_URL}/country/${codes}/indicator/${indicatorId}?format=json&date=2000:2024&per_page=500`;
const res = await fetch(url, { next: { revalidate: 86400 } }); // Cache 24h
const json = await res.json();
return json[1] || []; // [0] is metadata, [1] is actual data
}
No API key. No rate limits I've ever hit. No authentication. Just clean, free data for 200+ countries.
Key indicator codes I use:
| Code | Indicator |
|---|---|
NY.GDP.MKTP.CD |
GDP (USD) |
NY.GDP.PCAP.CD |
GDP per Capita |
SP.POP.TOTL |
Population |
SL.UEM.TOTL.ZS |
Unemployment Rate |
FP.CPI.TOTL.ZG |
CPI Inflation |
SP.DYN.LE00.IN |
Life Expectancy |
EN.ATM.CO2E.PC |
CO2 per Capita |
SE.ADT.LITR.ZS |
Literacy Rate |
IT.NET.USER.ZS |
Internet Users % |
The full app uses 50 indicators across 6 categories.
SEO Strategy: 1,290 Pages from 50 Countries
This is where it gets interesting. With 50 countries, you get C(50,2) = 1,225 unique comparison pairs. Add 50 country profile pages and some static pages, and you've got 1,290 indexable pages — all auto-generated at build time.
How It Works
// Generate all country-pair slugs
export function getAllCompareSlugs(): string[] {
const slugs: string[] = [];
for (let i = 0; i < COUNTRIES.length; i++) {
for (let j = i + 1; j < COUNTRIES.length; j++) {
slugs.push(`${COUNTRIES[i].slug}-vs-${COUNTRIES[j].slug}`);
}
}
return slugs;
}
// Next.js static generation
export function generateStaticParams() {
return getAllCompareSlugs().map((slug) => ({ slug }));
}
This generates URLs like:
/compare/united-states-vs-china/compare/south-korea-vs-japan/compare/germany-vs-france
Each page gets its own metadata and OG image:
export async function generateMetadata({ params }): Promise<Metadata> {
return {
title: `${countryA} vs ${countryB} - Economic Comparison`,
openGraph: {
images: [{
url: `/api/og?a=${countryA}&b=${countryB}`,
width: 1200,
height: 630,
}],
},
};
}
A dynamic sitemap feeds all 1,290 URLs to search engines with tiered priorities — homepage at 1.0, comparisons at 0.8, country profiles at 0.7.
Result: Every country pair is a long-tail keyword target. Someone Googling "USA vs China economy comparison" could land on a pre-built, data-rich page.
Free vs Pro
I wanted the core tool to be genuinely useful for free. Here's the split:
| Feature | Free | Pro ($9/mo) |
|---|---|---|
| Indicators | 5 (Overview) | 50 (all categories) |
| Countries | Compare 2 | Compare up to 10 |
| Data Range | 10 years (2014-2024) | 25 years (2000-2024) |
| PDF Reports | Watermarked, 5 indicators | Full 50-indicator report |
| CSV Export | — | Included |
| Ads | Yes | No |
The free tier gives you GDP, Population, Unemployment, Inflation, and Life Expectancy with 10 years of charts. That's enough for a quick comparison or school project.
Pro unlocks the deep dive: 50 indicators across Economy, Labor, Society, Energy, Environment, and Trade — plus multi-country comparison up to 10 countries on one chart.
Payment goes through Gumroad. License keys are verified server-side and stored as JWT tokens:
export function createToken(licenseKey: string): string {
return jwt.sign(
{ licenseKey, pro: true },
JWT_SECRET,
{ expiresIn: '7d' }
);
}
What I Learned
1. The World Bank API is an underrated goldmine
Free, no auth, well-structured, 200+ countries, 1,400+ indicators. If you're building anything data-related, check it out before scraping Wikipedia.
2. Combinatorics is your SEO friend
50 countries × 49 partners ÷ 2 = 1,225 unique pages. Each one is a potential search result. generateStaticParams() in Next.js makes this trivial.
3. Build the free tier first, pro features second
I built the complete 2-country comparison with 5 indicators first. It worked. It was useful. Only then did I add the paywall for power features. This keeps you honest about whether the core product has value.
4. Dark mode is table stakes in 2025
I almost skipped it. Glad I didn't. The implementation is straightforward with Tailwind's dark: prefix + a localStorage toggle + a script in <head> to prevent flash:
<script dangerouslySetInnerHTML={{ __html: `(function(){
try {
var t = localStorage.getItem('theme');
if (t === 'dark' || (!t && window.matchMedia('(prefers-color-scheme:dark)').matches)) {
document.documentElement.classList.add('dark');
}
} catch(e) {}
})()`}} />
5. PDF generation on the server is worth the effort
Using jspdf + jspdf-autotable on a Next.js API route means users get a clean, branded PDF without any client-side library loading. The Pro report generates a cover page, executive summary, and one data table per indicator — up to 50 pages of data.
Try It Out
- Live site: country-compare.com
- Example comparison: US vs China
- Country profile: South Korea
The free tier is fully functional — compare any 2 countries across 5 key indicators with interactive charts, right now.
Feedback welcome. What indicators or features would you want to see added?
Built in one day with Next.js, Chart.js, Tailwind CSS, and the World Bank API. Hosted for free on Vercel.
Top comments (0)