Build a NextJS UI for Subgraphs on Somnia

Subgraphs allow developers to efficiently query Somnia blockchain data using GraphQL, making it easy to index and retrieve real-time blockchain activity. In this tutorial, you’ll learn how to:

  • Fetch blockchain data from a Subgraph API

  • Fix CORS errors using a NextJS API route

  • Display token transfers in a real-time UI

By the end of this guide, you'll have a fully functional UI that fetches and displays token transfers from Somnia’s Subgraph API.

Prerequisites

Create a NextJS Project

Start by creating a new Next.js app.

npx create-next-app@latest somnia-subgraph-ui
cd somnia-subgraph-ui

Then, install required dependencies.

npm install thirdweb react-query graphql

Define the Subgraph API in Environment Variables

Create a .env.local file in the root folder.

NEXT_PUBLIC_SUBGRAPH_URL=https://proxy.somnia.chain.love/subgraphs/name/somnia-testnet/test-mytoken
NEXT_PUBLIC_SUBGRAPH_CLIENT_ID=YOUR_CLIENT_ID

💡 Note: Restart your development server after modifying .env.local:

npm run dev

Create a NextJS API Route for the Subgraph

Since the Somnia Subgraph API has CORS restrictions, we’ll use a NextJS API route to act as a proxy.

Inside the app directory create the folder paths api/proxy and add a file route.ts Update the route.ts file with the following code:

import { NextResponse } from "next/server";

const SUBGRAPH_URL = process.env.NEXT_PUBLIC_SUBGRAPH_URL as string;
const CLIENT_ID = process.env.NEXT_PUBLIC_SUBGRAPH_CLIENT_ID as string;

export async function POST(req: Request) {
  try {
    const body = await req.json();


    const response = await fetch(SUBGRAPH_URL, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Client-ID": CLIENT_ID, // ✅ Pass the Subgraph Client ID
      },
      body: JSON.stringify(body),
    });


    const data = await response.json();
    return NextResponse.json(data);
  } catch (error) {
    console.error("Proxy Error:", error);
    return NextResponse.json({ error: "Failed to fetch from Subgraph" }, { status: 500 });
  }
}

This code allows your frontend to make requests without triggering CORS errors.

Fetch and Display Token Transfers in the UI

Now that we have the API set up, let’s build a React component that:

  • Sends the GraphQL query using fetch()

  • Stores the fetched data using useState()

  • Displays the token transfers in a simple UI

To fetch the latest 10 token transfers, we’ll use this GraphQL query:

{
  transfers(first: 10, orderBy: blockTimestamp, orderDirection: desc) {
    id
    from
    to
    value
    blockTimestamp
    transactionHash
  }
}

This code fetches the last 10 transfers (first: 10) and orders them by timestamp (latest first). It retrieves wallet addresses (from, to) and the amount transferred (value) and includes the transaction hash (used to generate an explorer link).

Fetch and Display Data in a React Component

Now, let’s integrate the query into our NextJS frontend. Create a folder components and add a file TokenTransfer.ts

We use useState() to store transfer data and useEffect() to fetch it when the component loads.

"use client";
import { useEffect, useState } from "react";

export default function TokenTransfers() {
  // Store transfers in state
  const [transfers, setTransfers] = useState<any[]>([]);
  
  // Track loading state
  const [loading, setLoading] = useState(true);


Next, we fetch the token transfer data when the component loads.
 useEffect(() => {
    async function fetchTransfers() {
      setLoading(true); // Show loading state


      const query = `
        {
          transfers(first: 10, orderBy: blockTimestamp, orderDirection: desc) {
            id
            from
            to
            value
            blockTimestamp
            transactionHash
          }
        }
      `;


      const response = await fetch("/api/proxy", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ query }),
      });


      const { data } = await response.json();
      setTransfers(data.transfers || []); // Store results in state
      setLoading(false); // Hide loading state
    }


    fetchTransfers();
  }, []);

Once data is fetched, we render it inside the UI.

return (
    <div className="p-4">
      <h2 className="text-xl font-semibold mb-4">Latest Token Transfers</h2>
      
      {loading ? (
        <p>Loading transfers...</p>
      ) : (
        <ul>
          {transfers.map((transfer) => (
            <li key={transfer.id} className="mb-2 p-2 border rounded-lg">
              <p><strong>From:</strong> {transfer.from}</p>
              <p><strong>To:</strong> {transfer.to}</p>
              <p><strong>Value:</strong> {parseFloat(transfer.value) / 1e18} STT</p>
              <p>
                <strong>TX:</strong>{" "}
                <a
                  href={`https://shannon-explorer.somnia.network/tx/${transfer.transactionHash}`}
                  target="_blank"
                  className="text-blue-600 underline"
                >
                  View Transaction
                </a>
              </p>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

The UI shows a loading message while data is being fetched. When data is ready, it displays the latest 10 token transfers. It formats transaction values (value / 1e18) to show the correct STT amount and provides a link to view each transaction on Somnia Explorer.

Add the Component to the NextJS Page

Update the page.tsx file:

"use client";
import TokenTransfers from "../components/TokenTransfers";

export default function Home() {
  return (
    <main className="min-h-screen p-8">
      <h1 className="text-2xl font-bold">Welcome to MyToken Dashboard</h1>
      <TokenTransfers />
    </main>
  );
}

Restart the development server:

npm run dev

Now your NextJS UI dynamically fetches and displays token transfers from Somnia’s Subgraph! 🔥

Last updated