To show silver prices in Next.js, fetch /v1/spot/XAG-USD-SPOT from https://api.goldprice.dev in a Server Component. Keep your goldprice.dev key on the server and send it with Authorization: Bearer. Cache the request to control usage, and keep price strings intact for display or decimal arithmetic. Silver is quoted in USD per troy ounce. Free prices are 15 minutes late; paid plans are live.
Create the app and set your API key
Create an App Router project with npx create-next-app@latest silver-demo. Install the server guard with npm install server-only. These examples use fetch caching with Cache Components disabled. Set cacheComponents: false in next.config.ts if needed.
Get your API key through goldprice.dev. Add GOLDPRICE_API_KEY=YOUR_API_KEY to .env.local. Keep the name free of the NEXT_PUBLIC_ prefix so the key stays on the server.
Free includes 5,000 calls a month and 10 requests a minute, with no credit card. Silver prices on Free are 15 minutes late.
Write the silver price function
Save this as lib/goldprice.ts. The import "server-only" guard causes a build error if a Client Component imports the file. Read the API key here and keep it out of client props.
The function checks the HTTP status and validates the symbol and price fields. It keeps prices as strings and maps is_stale and computed_at to fields your page can use.
import "server-only";const API_BASE = "https://api.goldprice.dev";export interface SpotPrice { symbol: string; quoteCurrency: string; price: string; isStale: boolean; computedAt: string | null;}/** Fetch the latest spot price for one symbol, e.g. "XAG-USD-SPOT". */export async function getSpotPrice(symbol = "XAG-USD-SPOT"): Promise<SpotPrice> { const apiKey = process.env.GOLDPRICE_API_KEY; if (!apiKey) { throw new Error("GOLDPRICE_API_KEY is not set"); } const res = await fetch(`${API_BASE}/v1/spot/${symbol}`, { headers: { Authorization: `Bearer ${apiKey}` }, signal: AbortSignal.timeout(5000), next: { revalidate: 3600 }, }); if (res.status === 401) { throw new Error("goldprice: invalid API key"); } if (res.status === 429) { const retryAfter = res.headers.get("Retry-After"); throw new Error(`goldprice: rate limited, retry after ${retryAfter ?? "60"}s`); } if (!res.ok) { throw new Error(`goldprice: unexpected status ${res.status}`); } const raw = await res.json(); if (typeof raw.symbol !== "string" || typeof raw.price !== "string") { throw new Error("goldprice: unexpected response shape"); } return { symbol: raw.symbol, quoteCurrency: raw.quote_currency, price: raw.price, isStale: Boolean(raw.is_stale), computedAt: raw.computed_at ?? null, };}Call it from a Server Component and switch metals
Call getSpotPrice() directly from app/page.tsx. A Server Component can fetch the data itself. A Route Handler that only forwards the same request adds an extra network hop.
The same function accepts silver, gold and copper symbols. Silver and gold prices use USD per troy ounce. Copper uses USD per pound. The metal examples below belong inside a Server Component that imports getSpotPrice.
import { getSpotPrice } from "@/lib/goldprice";export default async function Page() { const spot = await getSpotPrice("XAG-USD-SPOT"); return ( <main> <h1> {spot.symbol}/{spot.quoteCurrency}: {spot.price} </h1> <p>Updated: {spot.computedAt}</p> </main> );}// Silver: USD per troy ounceconst silver = await getSpotPrice("XAG-USD-SPOT");// Gold: USD per troy ounceconst gold = await getSpotPrice("XAU-USD-SPOT");// Copper: USD per poundconst copper = await getSpotPrice("HG-USD-SPOT");Set caching for your call volume
With Cache Components disabled, next: { revalidate: 3600 } sets an hourly revalidation interval. This controls server caching. It does not refresh an open browser page automatically.
Count requests across symbols, development, previews and production against your quota. The comment below estimates usage for a single cached request; it is not a usage cap.
A shorter interval can show newer silver prices on paid plans, but uses more calls. Caching does not remove the delay on Free prices.
// Free-tier-safe default: ~720 calls/monthnext: { revalidate: 3600 }// Paid tier with quota to spare: near-livenext: { revalidate: 60 }Show a fallback when the request fails
Wrap the silver request in try/catch. This example logs the error on the server and shows a short message when the key is missing, the API rejects the request or the network times out.
The fallback does not retrieve a saved price. If you add a last known value, show its timestamp and check isStale so readers can judge its age.
import { getSpotPrice } from "@/lib/goldprice";export default async function Page() { let spot; try { spot = await getSpotPrice("XAG-USD-SPOT"); } catch (err) { console.error(err); return <p>Silver price is temporarily unavailable. Try again shortly.</p>; } return ( <main> <h1> {spot.symbol}/{spot.quoteCurrency}: {spot.price} </h1> </main> );}Expected output
{ "symbol": "XAG", "quote_currency": "USD", "unit": "troy_ounce", "contract_type": "spot", "price": "54.020", "is_stale": false, "computed_at": "2026-10-23T18:12:06.354573+00:00"}The page shows the silver price string in USD per troy ounce. The first example also shows computedAt; the fallback example omits it. The JSON shows the response a Free key receives. Use fresh response values for display.
Common errors
| Code | What you see | Fix |
|---|---|---|
| 401 | goldprice: invalid API key thrown from getSpotPrice() | The API rejected the key. Confirm GOLDPRICE_API_KEY matches your dashboard key and is set in .env.local or your production environment. Restart the development server after editing the file. A missing variable produces the separate GOLDPRICE_API_KEY is not set error. |
| 429 | goldprice: rate limited, retry after Ns | Check whether you reached the request rate limit or monthly quota. Honor Retry-After when retrying a rate limit. A depleted monthly quota needs a reset or a plan change. Increase the cache interval to reduce calls, and check pricing for plan limits. |
| N/A | A slow silver request delays the page | Keep AbortSignal.timeout(5000) in the fetch options to bound the network wait. Catch the timeout with the other request errors, as the fallback example does. Avoid immediate repeated retries, which can use more quota during an outage. |
| N/A | The API key uses NEXT_PUBLIC_GOLDPRICE_API_KEY | Use GOLDPRICE_API_KEY and read it only in the module guarded by import "server-only". Variables with the NEXT_PUBLIC_ prefix can reach browser JavaScript. Never pass the key to a Client Component. Replace any key that has already been exposed. |
Going further
- Show
isStaleandcomputedAtbeside the silver price - Use a decimal type to calculate silver values while preserving decimal precision
- Add a currency selector backed by
/v1/convert, usingXAGas the metal - Add a Suspense boundary so the rest of the page can render while the silver request finishes
- Track rate limit errors and API usage before shortening the cache interval