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.
mkdir hyperliquid-next-quickstart
cd hyperliquid-next-quickstartCopy the files below, or extract the downloaded archive into this directory (adjust the download path if needed):
tar -xzf ~/Downloads/next.tar.gzInstall the dependencies after saving or extracting the files:
npm installThe 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
{
"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
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
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
export default function Layout({ children }) {
return <html lang="en"><body>{children}</body></html>;
}app/page.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
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
npm run devOpen http://localhost:3000/api/price, or request it in a second terminal:
curl --fail http://localhost:3000/api/priceA successful response contains a positive price string, for example:
{"symbol":"BTC","price":"65000.5"}Check the production server with:
npm run build
npm startThe 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
502means the exchange request failed. Inspect server network access to the HTTPS endpoint inmarket.js, including regional restrictions. - If Next.js reports a missing React package, run
npm installfrom the directory containing this starter'spackage.json.