Next.js
Full-stack Next.js with route handlers for token minting and browser-side subscriptions.
Next.js is the recommended path — server components for one-shot reads, client components for live subscriptions, and a route handler that mints JWTs so your API key never leaves the server.
Install
npm i @mrdoge/node @mrdoge/client@mrdoge/node for the server (API key auth), @mrdoge/client for the
browser (JWT auth).
Server: mint tokens for the browser
import { NextResponse } from "next/server";
import { MrDoge } from "@mrdoge/node";
const mrdoge = new MrDoge({ apiKey: process.env.MRDOGE_API_KEY! });
export async function POST() {
// Optional: gate by your auth session
// const session = await auth();
// if (!session) return new Response("Unauthorized", { status: 401 });
const { token, expiresAt } = await mrdoge.tokens.create({ ttl: 600 });
return NextResponse.json({ token, expiresAt });
}Browser: subscribe to live matches
"use client";
import { useEffect, useState } from "react";
import { MrDoge, type Match } from "@mrdoge/client";
const mrdoge = new MrDoge({
authEndpoint: "/api/mrdoge/token",
});
export default function LivePage() {
const [matches, setMatches] = useState<Match[]>([]);
useEffect(() => {
let cancelled = false;
let sub: Awaited<ReturnType<typeof mrdoge.matches.subscribeLive>> | null =
null;
(async () => {
sub = await mrdoge.matches.subscribeLive({ sports: ["soccer"] });
if (cancelled) {
sub.cancel();
return;
}
setMatches(sub.snapshot);
sub.on("match.upd", (match) => {
setMatches((prev) => {
const i = prev.findIndex((m) => m.id === match.id);
if (i >= 0) {
const next = [...prev];
next[i] = match;
return next;
}
return [...prev, match];
});
});
sub.on("match.del", (matchId) => {
setMatches((prev) => prev.filter((m) => m.id !== matchId));
});
})();
return () => {
cancelled = true;
sub?.cancel();
};
}, []);
return (
<ul>
{matches.map((m) => (
<li key={m.id}>
{m.homeTeam.name} {m.stats?.homeScore}–{m.stats?.awayScore}{" "}
{m.awayTeam.name}
</li>
))}
</ul>
);
}Foreground wake-up (optional)
When the user tabs away and comes back, the WebSocket may be dead while the
SDK's reconnect loop is sleeping in backoff. Wire pingOrReconnect() to
visibilitychange so the first interaction after refocus is instant:
"use client";
import { useEffect } from "react";
import { mrdoge } from "@/lib/mrdoge";
useEffect(() => {
const onVisible = () => {
if (document.visibilityState === "visible") mrdoge.pingOrReconnect();
};
const onOnline = () => mrdoge.pingOrReconnect();
document.addEventListener("visibilitychange", onVisible);
window.addEventListener("online", onOnline);
return () => {
document.removeEventListener("visibilitychange", onVisible);
window.removeEventListener("online", onOnline);
};
}, []);pingOrReconnect() is a no-op when the socket is healthy, so it's cheap
to call on every event. See the
Subscriptions reference →
for full mechanics.
Server components: one-shot reads
For SSR / RSC, use @mrdoge/node directly — no client component, no
hydration cost:
import { MrDoge } from "@mrdoge/node";
const mrdoge = new MrDoge({ apiKey: process.env.MRDOGE_API_KEY! });
export default async function PicksPage() {
const { data: picks } = await mrdoge.ai.picks.list({
date: new Date().toISOString().slice(0, 10),
limit: 20,
});
return (
<ul>
{picks.map((pick) => (
<li key={pick.id}>
<strong>{pick.pickType}</strong> @ {pick.totalOdds.toFixed(2)}
<ul>
{pick.legs.map((leg, i) => (
<li key={i}>
{leg.outcome} — {leg.odds} ({leg.confidence})
</li>
))}
</ul>
</li>
))}
</ul>
);
}Pair with export const revalidate = 60 for ISR.
Environment variables
MRDOGE_API_KEY=sk_live_…Don't prefix with NEXT_PUBLIC_ — that exposes the key to the bundle.
Edge runtime
The route handler can run on the edge by exporting runtime:
export const runtime = "edge";@mrdoge/node doesn't work on edge (it opens WebSockets). Swap to
@mrdoge/http:
import { createHttpClient } from "@mrdoge/http";
const mrdoge = createHttpClient({ apiKey: process.env.MRDOGE_API_KEY! });
export async function POST() {
const { token, expiresAt } = await mrdoge.call("tokens.create", {
ttl: 600,
});
return Response.json({ token, expiresAt });
}