A full walk-through of building a production-ready matka results app with Next.js 16 App Router — server components, streaming, ISR, and a server-side API proxy that keeps your key secret.
Next.js 16 App Router is an excellent fit for a matka results site: server components give you instant first paint, the built-in fetch cache prevents duplicate calls, and Edge runtime lets you run your API proxy milliseconds from your users. This guide walks through a production-ready setup.
npx create-next-app@latest matka-results \
--typescript --tailwind --app --no-src-dir
cd matka-results
Add your API key to .env.local (never commit this):
MATKA_API_KEY=smk_live_abc123...
MATKA_API_BASE=https://sattamatkaapi.live
Never expose your API key to the browser. Instead, build a server-side proxy that adds the auth header and forwards the request:
// app/api/results/route.ts
import { NextRequest, NextResponse } from 'next/server'
export const runtime = 'edge'
export const revalidate = 30 // ISR: re-fetch at most every 30s
export async function GET(req: NextRequest) {
const url = new URL('/api/results/live', process.env.MATKA_API_BASE!)
// Forward query params (e.g. ?session=starline)
req.nextUrl.searchParams.forEach((v, k) => url.searchParams.set(k, v))
const upstream = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.MATKA_API_KEY}`,
'Content-Type': 'application/json',
},
next: { revalidate: 30 },
})
const data = await upstream.text()
return new NextResponse(data, {
status: upstream.status,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 's-maxage=30, stale-while-revalidate=60',
},
})
}
The s-maxage=30, stale-while-revalidate=60 headers tell Vercel/Cloudflare to cache the response for 30s, then serve stale for up to 60 more seconds while the next fetch happens in the background. This is the key to scaling — 1000 users hit the cached response, only 1 triggers an upstream API call.
// app/page.tsx
import { format } from 'date-fns'
type Result = {
market: { slug: string; name: string; session: string; openTime: string; closeTime: string }
openPana: string | null
closePana: string | null
jodi: string | null
resultString: string | null
isComplete: boolean
verified: boolean
}
async function getLiveBoard(): Promise<{ date: string; markets: Result[] }> {
const res = await fetch('http://localhost:3000/api/results', {
next: { revalidate: 30 },
})
if (!res.ok) throw new Error('Failed to fetch results')
return res.json()
}
export default async function Home() {
const board = await getLiveBoard()
return (
<main className="max-w-5xl mx-auto p-6">
<h1 className="text-3xl font-bold mb-1">Live Matka Results</h1>
<p className="text-sm text-gray-500 mb-6">
{format(new Date(board.date + 'T00:00:00+05:30'), 'dd MMM yyyy')} · IST
</p>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{board.markets.map((r) => (
<div key={r.market.slug} className="rounded-xl border p-4">
<div className="flex items-center justify-between mb-2">
<h2 className="font-semibold">{r.market.name}</h2>
<span className="text-xs text-gray-500">{r.market.session}</span>
</div>
<div className="font-mono text-2xl font-bold">
{r.resultString ?? 'Pending'}
</div>
<div className="text-xs text-gray-500 mt-1">
{r.market.openTime} – {r.market.closeTime}
</div>
</div>
))}
</div>
</main>
)
}
That is the entire server-rendered homepage. On first request, Next.js fetches the board, renders the HTML, and caches it. For the next 30 seconds, every visitor gets the cached HTML — zero API calls, zero DB hits.
For users who stay on the page, add a client component that polls for updates:
// components/LiveBoardRefresher.tsx
'use client'
import { useEffect } from 'react'
import { useRouter } from 'next/navigation'
export function LiveBoardRefresher() {
const router = useRouter()
useEffect(() => {
const tick = () => router.refresh() // re-fetch server component
const interval = setInterval(tick, 30000)
return () => clearInterval(interval)
}, [router])
return null
}
Drop <LiveBoardRefresher /> anywhere on the page. Every 30 seconds, it calls router.refresh() which re-runs the server component's fetch — but only if the cached response is stale. So if 100 users are on the page, only one of them triggers an actual upstream API call.
// app/markets/[slug]/page.tsx
import { notFound } from 'next/navigation'
export async function generateStaticParams() {
// Pre-render the top 20 markets at build time
const TOP_MARKETS = ['kalyan', 'main-bazar', 'milan-day', 'kalyan-night', ...]
return TOP_MARKETS.map(slug => ({ slug }))
}
export default async function MarketPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const res = await fetch(`https://sattamatkaapi.live/api/results/${slug}`, {
headers: { Authorization: `Bearer ${process.env.MATKA_API_KEY}` },
next: { revalidate: 30, tags: [`result-${slug}`] },
})
if (res.status === 404) notFound()
const data = await res.json()
return (
<main className="max-w-3xl mx-auto p-6">
<h1 className="text-3xl font-bold mb-2">{data.market.name}</h1>
<div className="font-mono text-4xl font-bold text-maroon">
{data.resultString ?? 'Pending'}
</div>
{/* ... rest of the page */}
</main>
)
}
The tags: [result-${slug}] line lets you on-demand revalidate the cache when a webhook fires (see Setting Up Webhooks for Auto Result Updates).
When a webhook arrives, call revalidateTag to instantly invalidate the cached result for that market:
// app/webhooks/matka/route.ts
import { revalidateTag } from 'next/cache'
import crypto from 'crypto'
export async function POST(req: Request) {
const body = await req.text()
const sig = req.headers.get('x-matka-signature') ?? ''
const expected = crypto
.createHmac('sha256', process.env.MATKA_WEBHOOK_SECRET!)
.update(body)
.digest('hex')
if (sig !== expected) return new Response('Bad signature', { status: 401 })
const event = JSON.parse(body)
if (event.type === 'result.declared') {
revalidateTag(`result-${event.data.market.slug}`)
revalidateTag('live-board')
}
return Response.json({ ok: true })
}
This is the killer combo: ISR for 30s staleness baseline + webhooks for instant invalidation on declaration. Users see new results within seconds of declaration, with zero polling.
Deploy to Vercel for the best Edge runtime support:
npx vercel
Set the MATKA_API_KEY and MATKA_WEBHOOK_SECRET environment variables in the Vercel dashboard. The Edge runtime deploys to ~30 POPs worldwide — every user gets sub-50ms latency to the proxy, and the cached board response is served from the nearest POP.
/api/results route handler that proxies the matka API and adds auth.s-maxage=30, stale-while-revalidate=60 for CDN caching.router.refresh() from a client component to poll on a 30s interval.generateStaticParams to pre-render top markets at build time.revalidateTag from your webhook handler to instantly invalidate cached pages on result declaration.This setup will serve thousands of concurrent viewers with a single Pro-plan API key, and stay sub-second fresh even when a market declares.
Get 2 days of full access to every endpoint — live results, history, charts, webhooks. No credit card required.
View pricing & start trialUse the Satta Matka Result API to pull today’s open, jodi, and close. Field names, sample JSON, old market IDs, and how the live board maps to the API.
A live Matka result API should update within seconds of open and close. How Satta Matka API polls DPBoss, stores today only, and pushes webhooks.
An auto result API fills your matka website when open and close are declared. Satta Matka API webhooks + JSON so staff stop typing numbers.