قیمت طلا در React و Next.js؛ پیادهسازی تابلو قیمت لحظهای طلا و سکه
نمایش قیمت طلا در React و Next.js با API بازار: Route Handler برای مخفی ماندن کلید، کش ۶۰ ثانیهای، کامپوننت تابلو، بهروزرسانی خودکار و فرمت اعداد فارسی.

- کلید API را در مرورگر قرار ندهید؛ در Next.js یک Route Handler بسازید که قیمتها را از API بگیرد و کش کند.
- کامپوننت React هر ۶۰ ثانیه از مسیر داخلی سایت شما میخواند و قیمت طلای ۱۸، مثقال و سکهها را نشان میدهد.
- اعداد را با Intl.NumberFormat و locale فارسی نمایش دهید و تغییر را با رنگ و جهت فلش نشان دهید.
یک تابلو قیمت طلا و سکه، چه برای سایت طلافروشی باشد چه برای داشبورد داخلی، سه نیاز اصلی دارد: عددها درست و تازه باشند، صفحه سریع بارگذاری شود و کلید API لو نرود. در این آموزش نمایش قیمت طلا در React را با Next.js (App Router) پیاده میکنیم؛ همین الگو با کمی تغییر در Vite و Create React App هم کار میکند.
app/api/gold/route.ts بسازید که با کلید سمت سرور از https://api.ezrate.me/v1/rates?symbols=g18,mesghal,ounce,emami,bahar بخواند و revalidate: 60 داشته باشد. کامپوننت کلاینت هر ۶۰ ثانیه /api/gold را fetch میکند. کلید هرگز به مرورگر نمیرسد.مفاهیم پایه (کلید، فیلدها، خطاها) در راهنمای کامل API قیمت ارز و طلا آمده و اینجا فقط پیادهسازی را میبینیم.
معماری: چرا یک لایهٔ سرور لازم است؟
هر چیزی که در کامپوننت کلاینت React بنویسید، در فایل جاوااسکریپت به دست کاربر میرسد. اگر کلید API را آنجا بگذارید، هر کسی با باز کردن DevTools آن را میبیند. Next.js این مشکل را با Route Handler حل میکند: درخواست به API روی سرور شما انجام میشود و مرورگر فقط نتیجه را میبیند. یک مزیت دیگر هم دارد: با کش ۶۰ ثانیهای، هزار بازدیدکنندهٔ همزمان فقط یک درخواست به API میسازند. توضیح کاملتر در امنیت API Key در فرانتاند.
گام ۱: Route Handler سمت سرور
// app/api/gold/route.ts
import { NextResponse } from "next/server"
const SYMBOLS = "g18,g24,mesghal,ounce,emami,bahar,nim,rob"
export async function GET() {
const res = await fetch(`https://api.ezrate.me/v1/rates?symbols=${SYMBOLS}&lang=fa`, {
headers: { "X-API-Key": process.env.EZRATE_KEY! },
next: { revalidate: 60 }, // کش سمت سرور: حداکثر یک درخواست در دقیقه
})
if (!res.ok) {
return NextResponse.json({ error: res.status }, { status: 502 })
}
const { data, meta } = await res.json()
return NextResponse.json({ data, as_of: meta.as_of })
}کلید را در فایل .env.local با نام EZRATE_KEY بگذارید. چون نام آن با NEXT_PUBLIC_ شروع نمیشود، Next.js آن را هرگز وارد باندل مرورگر نمیکند.
گام ۲: کامپوننت تابلو
// app/components/GoldBoard.tsx
"use client"
import { useEffect, useState } from "react"
type Rate = { symbol: string; name: string; buy: number; sell: number; change_pct: number; quote: string }
const fmt = new Intl.NumberFormat("fa-IR")
export default function GoldBoard() {
const [rates, setRates] = useState<Rate[]>([])
const [asOf, setAsOf] = useState("")
useEffect(() => {
const load = () =>
fetch("/api/gold").then(r => r.ok ? r.json() : null).then(j => {
if (j) { setRates(j.data); setAsOf(j.as_of) } // در خطا عدد قبلی میماند
})
load()
const t = setInterval(load, 60_000)
return () => clearInterval(t)
}, [])
return (
<table dir="rtl">
<thead><tr><th>نوع</th><th>خرید</th><th>فروش</th><th>تغییر</th></tr></thead>
<tbody>
{rates.map(r => (
<tr key={r.symbol}>
<td>{r.name}</td>
<td>{fmt.format(r.buy)}</td>
<td>{fmt.format(r.sell)}</td>
<td style={{ color: r.change_pct >= 0 ? "#0B8A55" : "#D1343F" }}>
{r.change_pct >= 0 ? "▲" : "▼"} {fmt.format(Math.abs(r.change_pct))}٪
</td>
</tr>
))}
</tbody>
<caption>بهروزرسانی: {asOf && new Date(asOf).toLocaleTimeString("fa-IR")}</caption>
</table>
)
}
گام ۳: رندر سمت سرور برای سئو
کامپوننت بالا بعد از بارگذاری صفحه عدد را میگیرد، پس موتور جستجو اولین بار جدول خالی میبیند. اگر صفحهٔ شما باید در گوگل برای «قیمت طلا» دیده شود، نسخهٔ اول را در Server Component بسازید و فقط بهروزرسانیهای بعدی را به کلاینت بسپارید:
// app/gold/page.tsx — دادهی اولیه روی سرور، بهروزرسانی روی کلاینت
async function getRates() {
const r = await fetch(`https://api.ezrate.me/v1/rates?symbols=g18,emami&lang=fa`, {
headers: { "X-API-Key": process.env.EZRATE_KEY! }, next: { revalidate: 60 } })
return r.ok ? (await r.json()).data : []
}
export default async function Page() {
const initial = await getRates()
return <GoldBoardLive initial={initial} /> // همان کامپوننت با مقدار اولیه
}خطاهای رایج
| مشکل | علت | راه حل |
|---|---|---|
| جدول خالی میماند | کلید در محیط production تعریف نشده | متغیر EZRATE_KEY را در تنظیمات هاست هم بگذارید |
| خطای 429 در ساعتهای شلوغ | کش غیرفعال است | مطمئن شوید revalidate تعیین شده و cache: "no-store" نگذاشتهاید |
| اعداد انگلیسی نمایش داده میشوند | locale تعیین نشده | از Intl.NumberFormat("fa-IR") استفاده کنید |
| Hydration mismatch | زمان یا عدد روی سرور و مرورگر فرق دارد | زمان را فقط بعد از mount رندر کنید |
طراحی تابلو برای طلافروشی: چه ستونهایی لازم است؟
تابلویی که برای مشتری طلافروشی طراحی میشود با داشبورد یک تحلیلگر فرق دارد. مشتری دنبال سه چیز است: قیمت گرم طلای ۱۸ عیار، قیمت سکهها و اینکه بازار امروز بالا رفته یا پایین. پیشنهاد ما:
| ردیف | نماد | چرا |
|---|---|---|
| طلای ۱۸ عیار (گرم) | g18 | مبنای قیمت زیورآلات |
| مثقال (آبشده) | mesghal | مرجع خرید و فروش عمده |
| انس جهانی | ounce | جهت بازار جهانی |
| سکه امامی، بهار آزادی | emami bahar | پرسششدهترین سکهها |
| نیم، ربع و گرمی | nim rob gerami | خرید کادویی |
اگر قیمت را برای فروش خود اعلام میکنید، باید سود یا ضریب خودتان را روی نرخ بازار اعمال کنید. این همان کاری است که تابلو قیمت EZRate با «ضریب تعدیل» انجام میدهد؛ منطق آن را در نمایش نرخ اختصاصی صرافی کنار نرخ بازار خواندهاید یا میخوانید.
نمایش تغییر قیمت با افکت چشمک
یکی از جزئیات کوچکی که تابلو را «زنده» نشان میدهد، رنگی شدن لحظهای ردیفی است که قیمتش عوض شده:
function Row({ r, prev }: { r: Rate; prev?: Rate }) {
const dir = prev && r.sell !== prev.sell ? (r.sell > prev.sell ? "up" : "dn") : ""
return (
<tr key={`${r.symbol}-${r.sell}`} className={dir ? `flash-${dir}` : ""}>
<td>{r.name}</td><td>{fmt.format(r.sell)}</td>
</tr>
)
}
/* CSS:
.flash-up { animation: up 1.2s } @keyframes up { from { background:#E3F4EC } }
.flash-dn { animation: dn 1.2s } @keyframes dn { from { background:#FBE7E8 } } */مقدار قبلی هر ردیف را در یک useRef نگه دارید و هنگام دریافت دادهٔ جدید با آن مقایسه کنید. تغییر key ردیف باعث میشود انیمیشن در هر تغییر دوباره اجرا شود.
حالت تمامصفحه برای تلویزیون
اگر تابلو را روی تلویزیون مغازه نمایش میدهید، چند نکته فرق میکند: فونت باید از سه متری خوانا باشد (حداقل ۴۸ پیکسل برای قیمت)، رنگ پسزمینه تیره باشد تا در نور مغازه چشم را اذیت نکند و صفحه هرگز به حالت خواب نرود. برای جلوگیری از خواب صفحه در مرورگرهای جدید از Wake Lock API استفاده کنید:
useEffect(() => {
let lock: any
;(navigator as any).wakeLock?.request("screen").then((l: any) => (lock = l)).catch(() => {})
return () => lock?.release()
}, [])اگر نمیخواهید این تابلو را خودتان توسعه و نگهداری کنید، راهنمای اتصال تلویزیون طلافروشی به قیمت لحظهای روش بدون کدنویسی را توضیح میدهد.
تست قبل از انتشار
- یکبار اینترنت را قطع کنید و ببینید تابلو آخرین عدد را با زمانش نگه میدارد.
- کلید را موقتاً اشتباه کنید و مطمئن شوید پیام خطای مفهومی در لاگ سرور ثبت میشود.
- صفحه را در موبایل باز کنید؛ جدول باید بدون اسکرول افقی خوانا باشد.
- در DevTools تب Network را ببینید: هیچ درخواستی نباید مستقیم به api.ezrate.me برود.

نرخهای لحظهای مرتبط با این مطلب: قیمت طلای ۱۸ عیار، قیمت سکه امامی، قیمت دلار.

نظرات (۰)
نظرها پس از تأیید نمایش داده میشوند؛ برای ارسال، ورود به حساب لازم است.


