feat: add CoinGecko API key support with env-based attribution and docs updates

This commit is contained in:
2026-03-08 18:24:33 +01:00
parent 3c158963a5
commit 82b676d9c5
4 changed files with 78 additions and 6 deletions

View File

@@ -1,3 +1,12 @@
# No API keys are required for the default setup. # Optional: CoinGecko API key (server-side only).
# Preferred:
# COINGECKO_PRO_API_KEY=
# or
# COINGECKO_DEMO_API_KEY=
#
# Fallback generic option:
# COINGECKO_API_KEY=
# COINGECKO_API_KEY_TYPE=demo # demo | pro
#
# Optional: override the internal API route base when deploying behind a proxy. # Optional: override the internal API route base when deploying behind a proxy.
# NEXT_PUBLIC_API_BASE_URL= # NEXT_PUBLIC_API_BASE_URL=

View File

@@ -118,7 +118,22 @@ npm run sync:crypto-icons
## Environment Variables ## Environment Variables
No API keys are required. CoinGecko key is optional but recommended to reduce rate-limit issues.
Preferred server-side variables:
```env
COINGECKO_PRO_API_KEY=
# or
COINGECKO_DEMO_API_KEY=
```
Fallback generic option:
```env
COINGECKO_API_KEY=
COINGECKO_API_KEY_TYPE=demo # demo | pro
```
Optional variable (only if you want to call API routes through a custom base URL): Optional variable (only if you want to call API routes through a custom base URL):

View File

@@ -222,7 +222,25 @@ export function ConverterCard({
variant="outline" variant="outline"
className="border-border/70 bg-background/50" className="border-border/70 bg-background/50"
> >
Rates: {data.sources.fiat} {data.sources.crypto} <span className="text-muted-foreground">Fiat rates by</span>
<a
href="https://frankfurter.dev/"
target="_blank"
rel="noreferrer"
className="ml-1 text-foreground transition-colors hover:text-cyan-100"
>
{data.sources.fiat}
</a>
<span className="mx-1 text-muted-foreground"></span>
<span className="text-muted-foreground">Price data by</span>
<a
href="https://www.coingecko.com/"
target="_blank"
rel="noreferrer"
className="ml-1 text-foreground transition-colors hover:text-cyan-100"
>
{data.sources.crypto}
</a>
</Badge> </Badge>
</div> </div>
<CardDescription className="pr-1 text-base/7 sm:text-sm"> <CardDescription className="pr-1 text-base/7 sm:text-sm">

View File

@@ -7,6 +7,38 @@ export interface CryptoRateResult {
const COINGECKO_BASE_URL = "https://api.coingecko.com/api/v3"; const COINGECKO_BASE_URL = "https://api.coingecko.com/api/v3";
function buildCoinGeckoHeaders(): HeadersInit {
const headers: Record<string, string> = {
accept: "application/json"
};
const proApiKey = process.env.COINGECKO_PRO_API_KEY?.trim();
const demoApiKey = process.env.COINGECKO_DEMO_API_KEY?.trim();
const genericApiKey = process.env.COINGECKO_API_KEY?.trim();
const genericApiKeyType =
process.env.COINGECKO_API_KEY_TYPE?.trim().toLowerCase() ?? "demo";
if (proApiKey) {
headers["x-cg-pro-api-key"] = proApiKey;
return headers;
}
if (demoApiKey) {
headers["x-cg-demo-api-key"] = demoApiKey;
return headers;
}
if (genericApiKey) {
if (genericApiKeyType === "pro") {
headers["x-cg-pro-api-key"] = genericApiKey;
} else {
headers["x-cg-demo-api-key"] = genericApiKey;
}
}
return headers;
}
export async function fetchCryptoData(): Promise<CryptoRateResult> { export async function fetchCryptoData(): Promise<CryptoRateResult> {
const ids = CRYPTO_ASSETS.map((asset) => asset.providerId).filter( const ids = CRYPTO_ASSETS.map((asset) => asset.providerId).filter(
(id): id is string => Boolean(id) (id): id is string => Boolean(id)
@@ -18,9 +50,7 @@ export async function fetchCryptoData(): Promise<CryptoRateResult> {
const response = await fetch(url, { const response = await fetch(url, {
next: { revalidate: 60 }, next: { revalidate: 60 },
headers: { headers: buildCoinGeckoHeaders()
accept: "application/json"
}
}); });
if (!response.ok) { if (!response.ok) {