Mr. Doge

Guides

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

app/api/mrdoge/token/route.ts
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

app/live/page.tsx
"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:

app/live/page.tsx
"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:

app/picks/page.tsx
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

.env.local
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 });
}

Next

On this page

Next.js