Tutorial/Next.js and TypeScript

Exchange rates in Next.js and TypeScript

Fetch live exchange rates from a Next.js Server Component, keep the API key off the client, and cache the response without hiding when the underlying rate last changed.

MMexchangerate.dev·Jul 17, 2026·6 min read

Call exchangerate.dev from a Server Component or Route Handler, never from browser code that contains your key. Next.js can revalidate the upstream request every 60 seconds while the response fields source, market_session, and data_updated_at tell your UI what the cached rate actually represents.

Key points
Keep EXCHANGERATE_API_KEY in a server-only environment variable. Never prefix it with NEXT_PUBLIC_.
Use Next.js server-side fetch with next: { revalidate: 60 } to avoid one upstream request per page view.
Display data_updated_at rather than treating the response timestamp as proof that the market observation is fresh.
Use indicative FX rates for display and analytics. Let the payment provider determine the executable settlement amount.

Fetch from the server side

A Server Component runs outside the browser, so it can read a private environment variable. The example below requests only EUR and GBP against USD, checks the HTTP status, and returns typed JSON to the page.

typescript · app/page.tsxcopy
type LatestRates = {
  base: string;
  source: "live" | "ecb_daily" | "fred_daily";
  market_session: string;
  data_updated_at: string;
  rates: Record<string, number>;
};

async function getRates(): Promise<LatestRates> {
  const apiKey = process.env.EXCHANGERATE_API_KEY;
  if (!apiKey) throw new Error("EXCHANGERATE_API_KEY is missing");

  const response = await fetch(
    "https://api.exchangerate.dev/v1/latest/USD?symbols=EUR,GBP",
    {
      headers: { Authorization: `Bearer ${apiKey}` },
      next: { revalidate: 60 },
    },
  );

  if (!response.ok) {
    throw new Error(`FX request failed: ${response.status}`);
  }

  return response.json() as Promise<LatestRates>;
}

export default async function Page() {
  const fx = await getRates();

  return (
    <main>
      <p>1 USD = {fx.rates.EUR} EUR</p>
      <small>
        {fx.source} · {fx.market_session} · updated {fx.data_updated_at}
      </small>
    </main>
  );
}

Cache the request, preserve the observation time

next: { revalidate: 60 } gives the upstream request a cache lifetime of at most 60 seconds. That reduces duplicate calls across page views. It does not make a daily reference rate intraday-fresh, so keep source, market_session, and data_updated_at beside the value.

Do not expose the key
Environment variables prefixed with NEXT_PUBLIC_ can be included in browser bundles. Name this secret EXCHANGERATE_API_KEY and read it only from server code. Anonymous calls are fine for a quick test, but production code should use a key.

Separate display currency from settlement currency

A storefront can use an indicative rate to show an approximate local price. The final charge must come from the payment provider or dealing venue because fees, spreads, rounding, and settlement timing can change the executable amount.

  • Keep the API key on the server.
  • Cache successful responses, not 401 or 429 errors.
  • Show the rate source and observation time when freshness matters.
  • Keep the original product price and currency as the settlement source of truth.
MM
exchangerate.dev
Integration guides for developers building with FX data.

Keep reading

TutorialCurrency conversion in JavaScript and Node.jsRead ReferenceReading source and market_sessionRead GuideIndicative vs executable FX ratesRead