ETF comparison website

Build an ETF comparison website with a page per ISIN

Comparison sites help investors weigh ETFs side by side: what each one tracks, what it costs, how big it is and how it has behaved. The best ones have a page per ISIN and a comparison view for any pair. This page covers the data model, the SEO structure and how to keep thousands of pages current from a single API.

The problem

An ETF comparison site is a content site with a data problem. Readers arrive from a search for a specific ETF or a specific pairing ("A vs B"), and they expect the page to show current facts: TER, fund size, structure, distribution policy, index tracked, top holdings and recent performance. Multiply that by the number of UCITS ETFs in Europe and by the number of pairs people search for, and hand-maintained pages are impossible.

The second problem is trust. A comparison page that shows stale fees or a holdings table from eighteen months ago is worse than no page at all.

What you need from the data

Per ETF:

  • Name, ISIN, share class, currency and distribution policy
  • Index tracked (benchmark name) and structure
  • TER, fund size, inception date
  • Top holdings, sector and country breakdowns
  • Calendar returns, cumulative returns and an indexed series for a chart
  • Risk rating and volatility
  • As-of date

For the comparison view you need the same fields for both ETFs and a consistent layout so differences are readable at a glance.

How it works with FundFacts API

Use static generation with periodic revalidation. Each ETF page and each comparison page is built from cached data objects; a scheduled job re-loads ISINs when their expiresAt passes and triggers a rebuild of the affected pages.

typescript
const HEADERS = { Authorization: `Bearer ${process.env.FUNDFACTS_API_KEY}` };
async function fund(isin: string) {
const res = await fetch(`https://fundfactsapi.com/api/v1/funds/${isin}`, { headers: HEADERS });
if (!res.ok) throw new Error(`${res.status} loading ${isin}`);
return res.json();
}
function pick(f: any) {
return {
isin: f.isin as string,
name: f.name as string,
index: f.data.benchmarkName,
structure: f.data.structure,
ter: f.data.headlineMetrics?.ter,
aum: f.data.headlineMetrics?.aum,
distribution: f.data.keyFacts?.distribution,
inception: f.data.keyFacts?.inception,
riskRating: f.data.riskRating,
holdings: f.data.keyFacts?.holdings,
oneYear: f.data.cumulativePerformance?.["1y"],
dataAsOf: f.data.dataAsOf,
};
}
export async function comparison(a: string, b: string) {
const [fa, fb] = await Promise.all([fund(a), fund(b)]);
return { left: pick(fa), right: pick(fb) };
}
// comparison("IE00B4L5Y983", "IE00BJ0KDQ92") -> two MSCI World trackers side by side

Render left and right as two columns of the same rows. Where both funds track the same index, the interesting rows are TER, size, structure and distribution; where they track different indices, lead with the benchmark and the exposure breakdowns.

Design notes

SEO structure. One canonical URL per ISIN and one per pair, with the alphabetically smaller ISIN first so "A vs B" and "B vs A" resolve to the same page. Put the fund name, ISIN and as-of date in the title and first paragraph. The ISIN guide explains why the share-class ISIN, not the ticker, should be the key.

Caching. The API refreshes every 24 hours. Persist each response with expiresAt, rebuild pages from the store and never call the API during a page request. A site covering a few thousand ISINs needs a Scale or Enterprise plan for a full daily refresh; alternatively refresh popular pages daily and the long tail weekly.

Quota and errors. Run the refresh as a queue that respects X-RateLimit-Remaining and pauses on 429. Cold ISINs take one to three minutes on first load, so build the initial catalogue over several days rather than in one burst. A 404 fund_not_found means the page should be removed or marked as unavailable; a 502 upstream_error means keep the previous version and retry later.

Editorial content. The API provides facts; your value is the explanation around them. Write about what an index covers, what a replication method means, why TER differs from total cost of ownership. Keep hand-written text and API data in separate blocks so a data refresh never overwrites prose.

Disclaimers. Show dataAsOf on every page, state that figures are read from the documents each fund house publishes, and include the standard wording that this is not investment advice and past performance is not a guide to future returns. Avoid "best ETF" framing; present differences and let the reader decide.

The fund comparison tool guide walks through the comparison table in more detail.

Frequently asked questions

Can I call the API from the browser on each page view?

You should not. Your API key would be exposed, every visitor would consume quota and cold ISINs would leave visitors waiting for minutes. Build pages server-side from a store you refresh on a schedule. The 24-hour refresh cycle and the expiresAt field make this straightforward: rebuild a page only when its underlying record has expired and been re-loaded.

How do I handle ETFs that trade on several exchanges?

Key everything on the share-class ISIN, which is the same on every exchange, and treat tickers as display aliases. A single page for IE00B4L5Y983 can list the London, Xetra and Milan tickers in a table without duplicating content. This avoids thin duplicate pages and matches how the fund house itself identifies the product in its documents.

Can I show performance figures on a public site?

Yes, with care. Display the calendar-year and cumulative figures with their dataAsOf date, state that they are read from the fund house's published documents, and include the usual statement that past performance is not a reliable guide to future returns. Do not compute or imply rankings based on returns, and do not describe any fund as a good or bad choice.

How large a catalogue can I maintain?

A full daily refresh needs one request per ISIN. Scale covers 1,000 requests a day and Enterprise is metered at $0.01 per request for larger catalogues. Most sites do not need every page refreshed daily: refresh the pages that receive traffic each day and the long tail on a weekly rotation, and the effective catalogue size is several times the daily quota.

Build your etf comparison website on FundFacts API

Free plan with 10 requests a day to prototype; paid plans add the AI companion kit that scaffolds the integration for you.