# Next.js App Router with CurrencyCore

> Add server-only CurrencyCore routes to Next.js, keep the API key off the client, and cache daily reference rates with revalidate.

Canonical: https://currency-core.com/docs/guides/nextjs/
Updated: 2026-11-11
Published: 2026-11-11

---
Call CurrencyCore from a Route Handler or a Server Action, never from a Client Component. The key stays in `process.env` on the server, the browser receives only your response, and the Next.js data cache can hold a dated rate snapshot instead of refetching it for every visitor.

The example targets the App Router on Node.js 18 or later. Free includes 1,000 requests per month at 60 per minute. Live keys require a paid plan; test keys share the data and quota and are always capped at 60 per minute.

Keep the variable server-only. A `NEXT_PUBLIC_` prefix inlines the value into the client bundle.

This route uses `fetch` directly instead of `@currencycore/sdk` for one reason: Next.js caching is configured through the `next` option on `fetch`, and the generated SDK does not expose that option. Everywhere else, the SDK is the shorter path.

```ts

export async function GET(request: Request) {
  const key = process.env.CURRENCYCORE_API_KEY;
  if (!key) {
    return NextResponse.json(
      { error: { code: "missing_key", message: "Set CURRENCYCORE_API_KEY" } },
      { status: 500 },
    );
  }

  const { searchParams } = new URL(request.url);
  const url = new URL("https://api.currency-core.com/v1/convert");
  url.searchParams.set("from", searchParams.get("from") ?? "USD");
  url.searchParams.set("to", searchParams.get("to") ?? "INR");
  url.searchParams.set("amount", searchParams.get("amount") ?? "100");

  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${key}`, Accept: "application/json" },
    signal: AbortSignal.timeout(10_000),
    next: { revalidate: 21_600 },
  });

  const body: unknown = await res.json();
  return NextResponse.json(body, { status: res.status });
}
```

A rate table is the same handler pointed at `/v1/rates/EUR`. The two responses look different:

```json
{
  "from": "USD",
  "amount": 100,
  "date": "2026-11-10",
  "results": [{ "to": "INR", "result": 8345.0, "rate": 83.45 }]
}
```

```json
{
  "base": "EUR",
  "date": "2026-11-10",
  "rates": { "EUR": 1, "USD": 1.0861, "INR": 91.62 }
}
```

Returning the upstream status keeps `400`, `401`, `403`, and `429` visible to your client. Do not log the `Authorization` header. `next.revalidate` caches per URL, so every amount and target combination is a separate entry.

Caching behavior depends on the Next.js version and on whether the route uses dynamic APIs. In Next.js 15, a Route Handler that reads the `Request` is dynamic, while an explicit `next.revalidate` on the `fetch` still caches that request. Verify the cached response in your build, and prefer a stable key such as `base + date` over a free-form amount when you need a long TTL.

A Server Action uses the same `fetch`. Keep it in a `"use server"` module or mark the function, and validate the amount before forwarding it.

## Next steps

- [API reference](/docs/api/) and [OpenAPI specification](/docs/openapi/)
- [Authentication](/docs/authentication/) and [error codes](/docs/errors/)
- [Caching guide](/docs/guides/caching/) for dated snapshots
- [All developer guides](/docs/guides/)
