Skip to content

Next.js server quickstart ​

Serve a public BTC price from a Next.js App Router Route Handler using @siebly/hyperliquid-api. No API key is needed. Prices are BTC mid prices. This example runs in the Node.js server runtime.

Create the project ​

Use Node.js with npm. Download the complete Next.js starter, or create an empty directory and copy all files below.

sh
mkdir hyperliquid-next-quickstart
cd hyperliquid-next-quickstart

Copy the files below, or extract the downloaded archive into this directory (adjust the download path if needed):

sh
tar -xzf ~/Downloads/next.tar.gz

Install the dependencies after saving or extracting the files:

sh
npm install

The SDK installs from latest. The starter uses Next.js 16.4.5 and React 19.2.0, matching the server recipe in Runtime compatibility.

Project files ​

package.json ​

json
{
  "name": "hyperliquid-next-quickstart",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "next dev --webpack",
    "build": "next build --webpack",
    "start": "next start"
  },
  "dependencies": {
    "@siebly/hyperliquid-api": "latest",
    "next": "16.4.5",
    "react": "19.2.0",
    "react-dom": "19.2.0"
  }
}

market.js ​

js
export const endpoints = {"rest": "https://api.hyperliquid.xyz/info", "websocket": "wss://api.hyperliquid.xyz/ws"};
export function client(sdk) { return new sdk.RestClient({}, {timeout:15000}); }
export async function read(client) { return (await client.getAllMids()).BTC; }
export function stream(sdk, logger) { return new sdk.WebsocketClient({}, logger); }
export const event = 'message';
export function value(message) { return message.channel === 'allMids' ? message.data?.mids?.BTC : undefined; }
export async function subscribe(ws) { await ws.subscribe({ type: 'allMids' }); }

price.js ​

js
import * as sdk from '@siebly/hyperliquid-api';
import * as market from './market.js';

function positivePrice(value) {
  if (!Number.isFinite(Number(value)) || Number(value) <= 0) {
    throw new Error('The exchange did not return a positive BTC price.');
  }
  return String(value);
}

export async function readPrice() {
  return positivePrice(await market.read(market.client(sdk)));
}

// Returns cleanup without waiting for subscription acknowledgement.
export function watchPrice(onPrice, onError) {
  const ws = market.stream(sdk);
  let stopped = false;
  const receive = message => {
    const value = market.value(message);
    if (!stopped && value !== undefined) {
      clearTimeout(deadline);
      try { onPrice(positivePrice(value)); } catch (error) { fail(error); }
    }
  };
  const fail = error => { if (!stopped) onError(error); };
  const deadline = setTimeout(() => fail(new Error('No BTC update within 30 seconds. Check network access.')), 30000);
  ws.on(market.event, receive);
  ws.on('exception', fail);
  // React may clean up an effect before its queued subscription starts.
  Promise.resolve().then(async () => {
    if (stopped) return;
    try {
      await market.subscribe(ws);
    } finally {
      // A connection may finish opening after cleanup was requested.
      if (stopped) ws.closeAll();
    }
  }).catch(fail);
  return () => {
    if (stopped) return;
    stopped = true;
    clearTimeout(deadline);
    ws.closeAll();
    ws.removeListener(market.event, receive);
    ws.removeListener('exception', fail);
  };
}

app/layout.js ​

js
export default function Layout({ children }) {
  return <html lang="en"><body>{children}</body></html>;
}

app/page.js ​

js
export default function Page() {
  return <main><h1>BTC price</h1><a href="/api/price">Read the current price</a></main>;
}

app/api/price/route.js ​

js
import { readPrice } from '../../../price.js';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

export async function GET() {
  try {
    return Response.json({ symbol: 'BTC', price: await readPrice() });
  } catch (error) {
    console.error('BTC price request failed:', error.message || String(error));
    return Response.json({ error: 'Exchange price request failed. Check the server network and logs.' }, { status: 502 });
  }
}

Run ​

sh
npm run dev

Open http://localhost:3000/api/price, or request it in a second terminal:

sh
curl --fail http://localhost:3000/api/price

A successful response contains a positive price string, for example:

json
{"symbol":"BTC","price":"65000.5"}

Check the production server with:

sh
npm run build
npm start

The handler makes a fresh REST request for each call. It does not open a WebSocket. To receive a WebSocket update in a standalone process, use the Bun or Deno example, which closes its subscription explicitly. The Vite example shows browser subscription cleanup.

Troubleshooting ​

  • Keep runtime = 'nodejs' in the handler. Client Components and the Edge runtime are outside this recipe and its server verification.
  • A 502 means the exchange request failed. Inspect server network access to the HTTPS endpoint in market.js, including regional restrictions.
  • If Next.js reports a missing React package, run npm install from the directory containing this starter's package.json.

Built by Siebly. Open source SDKs for exchange APIs.