TutorialsCode

Silver price in Next.js

Show silver prices in a Next.js Server Component. Keep your key on the server, cache requests and preserve decimal price strings.

23 Oct 2026 · 4 min read

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.

lib/goldprice.ts
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.

app/page.tsx
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>  );}
app/page.tsx
// 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.

lib/goldprice.ts · revalidate
// 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.

app/page.tsx · with fallback
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

JSON
{  "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

CodeWhat you seeFix
401goldprice: 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.
429goldprice: rate limited, retry after NsCheck 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/AA slow silver request delays the pageKeep 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/AThe API key uses NEXT_PUBLIC_GOLDPRICE_API_KEYUse 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 isStale and computedAt beside the silver price
  • Use a decimal type to calculate silver values while preserving decimal precision
  • Add a currency selector backed by /v1/convert, using XAG as 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

Questions

Will hourly caching fit the free tier quota?

Hourly caching can help keep silver requests within Free's 5,000 calls a month and 10 requests a minute. It does not enforce those limits. Count calls across symbols, development, previews and production, then check your usage before shortening the interval.

Do I need a Route Handler for silver prices?

No. A Server Component can call getSpotPrice() directly while rendering. Keep the API key in the server module. Add a Route Handler when your browser code needs an endpoint, and control access and request volume there.

Does this work with Cache Components enabled?

These examples use fetch caching with Cache Components disabled. If your app enables cacheComponents, use a function marked "use cache" and set its lifetime with cacheLife(). Follow the Next.js caching guide for that model before adapting the silver request.

Are the silver prices live?

Paid plans return live silver prices. Free and keyless prices are 15 minutes late. Your page's cache can add more age, so choose its lifetime carefully. Responses include computed_at and is_stale; use them when showing freshness.

Why keep the silver price as a string?

The API returns prices as decimal strings. Keep the string when displaying the silver quote, and use a decimal type for arithmetic. Converting it to a JavaScript Number can introduce rounding errors because binary floating point cannot represent every decimal value exactly.

Can Claude get silver prices through MCP?

Yes. Claude can use get_spot_price through the goldprice.dev MCP server to request silver prices. Connect to https://api.goldprice.dev/v1/mcp/ without a key, or run npx @nusantara-ventures/goldprice-mcp with your key. Free and keyless prices are 15 minutes late; paid plans are live.

Can I get silver prices in another currency?

Yes. Silver spot symbols only use USD. For euros per gram, call GET /v1/convert?from=XAG&to=EUR&amount=1&unit=gram. It works without a key, with prices 15 minutes late and a limit of 100 requests an hour per IP. Supported units are oz, gram and kg.

Keep reading

Start with a free API key

5,000 requests a month on the free plan. No credit card.

Get a free key