Test Category

Test Blog Post

Starter template for writing out a blog post using MDX/JSX and Next.js.

No Name Exists

Abdullah Muhammad

Published on May 17, 20265 min read 1 views

Share:
Article Cover Image

Introduction

In the past couple of tutorials, we did a walk through of the Next.js framework, Vercel components, and tools related to integrating AI into your development workflow.

In the code overview and demo sections, we implemented portions of a web project (Ethereum dashboard) which I had been re-building using Next.js.

Of course, aside from the world of web development, I am familiar with the crypto and web3 space and decided to create this project to make it easier for new people to navigate the Ethereum blockchain.

There is a lot to learn when it comes to the blockchain space and even for traditional web2 developers, it is a paradigm shift from the usual way of development.

I do not intend on diving deep into the key aspects related to Ethereum (there are many resources out there for that), but I will do a walk through of how I created a one-stop site for users to view all things related to Ethereum and beyond.

Project Dependencies

When installing and configuring Next.js, we use the npx create-next-app@latest . command. There are quite a few dependencies that come with Next.js.

The following is a list of key dependencies when working with Next.js:

This is the minimum setup required for working with Next.js applications.

When building out the dashboard, there are additional libraries that are needed for development.

Required dependencies are listed below along with a short description:

  • Vercel AI SDK — Working with different AI models for ease of access
  • Vercel Analytics — Incorporating page visits and data related to visitors
  • DayJS — Date handling for data related to charting
  • Firecrawl API — Web scrape pages for LLM-ready data
  • Lucide-React — Icon library for utilizing ready-made icons
  • Recharts — Efficient graph library for data related to coin pricing
  • Shadcn/ui — Component library for Next.js
  • SWR — Similar to @tanstack/react-query, but optimized for Next.js
  • Zod — Library for enforcing schemas for the TypeScript language

So far, we have covered the AI SDK, Analytics, DayJS, Firecrawl API, and the Zod libraries.

Icon and component libraries such as Lucide-React and Shadcn/ui contain comprehensive documentation and are easy to understand and use (we will cover them in the future).

So, the focus of this tutorial will be on features related to graphing and efficient data fetching using the Recharts and SWR libraries.

"You said your dashboard is related to Ethereum so where does your data come from?"

Great question! Acting as a data aggregator, the dashboard collects information and formats it using well-known, secure APIs.

The next section describes the different API providers and what their APIs are used for.

Crypto API Providers

We will touch on the different API providers used in the development of the Ethereum dashboard.

Most of the these providers consist of a set of free APIs as well as paid APIs. Paid APIs have their own benefits such as enhanced rate limits, higher request volume, additional endpoints, and so on.

For the dashboard, I bought the paid version of the CoinGecko API. The team reached out and offered me a 50% discount on year one of usage and along with it, I get additional API keys and endpoints to work with.

The following is a list of the API providers used in this project:

  • Alchemy — Blockchain data provider for wallet balances, transactions
  • BlockNative — Real-time Ethereum gas price monitoring
  • BeaconChain — Insights into Ethereum staking and validators
  • CoinGecko — Fetch live market data, trending coins, and pricing details
  • Etherscan — Transaction details and blockchain explorer integration
  • Moralis — Multi-chain support for NFTs and tokens
  • Opensea — Insights into trending NFT collections, metadata, and so on
  • Transpose — Advanced analytics and wallet history queries

A combination of these eight providers allows anyone to build a comprehensive cryptocurrency dashboard.

I built one for Ethereum as a passion project as well as for other famous Layer Two solutions (Arbitrum, Optimism, and Polygon).

Extensive documentation for each provider can be found by clicking each of their respective titles above.

In the next section, we will cover the different components that make up the Ethereum ecosystem as well as the different features of the dashboard that allow users to view data related to these key components.

Ethereum Ecosystem

There is a lot to know when it comes Ethereum. Not only is it the largest “altcoin” aside from Bitcoin, there are key components that make up its ecosystem as a whole.

As Ethereum merged into the new protocol known as, Proof-of-Stake, users can contribute to the ecosystem by staking their Ethereum for a certain amount of APY while also ensuring the network is secure.

From market prices, decentralized finance, digital assets, and web3 development, there is so much opportunity in Ethereum.

The following list describes key components that make up Ethereum:

  • DeFi (Staking and Validators) — Stake to secure the Ethereum ecosystem
  • EIP Protocols — Standard protocols related to Ethereum development
  • ERC-20 Tokens — Fungible tokens deployable via smart contracts on the Ethereum blockchain
  • ERC-721 Tokens — Non-fungible tokens deployable via smart contracts on the Ethereum blockchain
  • ERC-1155 Tokens — Semi-fungible tokens deployable via smart contracts on the Ethereum blockchain
  • ENS Domains — Similar to web2, web3 domains are simply ERC721 tokens that connect to an Ethereum wallet, upon mint (.eth domains)
  • Ethereum Gas — The required amount to complete a transaction on Ethereum
  • Layer Two Solutions — Scaling solutions related to Ethereum for cost-effectiveness and efficiency purposes
  • Market Prices — ETH, Ethereum’s native gas token can be bought and sold on the market

And so much more.

If you do not have a clue about Ethereum or how the blockchain works, you will find this list confusing. I tried to best summarize each of these key components.

You can learn more about Ethereum using this official guide.

Ethereum Dashboard Components

Building on the last section, the Ethereum dashboard aims to provide users with information related to these key components.

The APIs do all the heavy lifting and we simply use Next.js/React to display this information to the users.

Most of the components are information tables that format and tabulate the data provided by the APIs.

There is also a section (as we saw in the last tutorial) where the dashboard makes use of the Firecrawl API and the AI SDK to produce real-time market insights.

The following list summarizes what the dashboard allows users to lookup:

  • DeFi, Staking, and Validator Information
  • EIP Protocols
  • ENS Lookups (Address to ENS, ENS to Address, Name, and by ID)
  • ERC-20 Collection Analytics
  • ERC-20 Holdings by Wallet
  • ERC-20 Token Lookups
  • ERC-721 Collection Analytics
  • ERC-721 Holdings by Wallet
  • ERC-721 Token Lookups
  • Ethereum Gas Metrics
  • Layer Two Solutions
  • Pricing and ERC-20 Token Pricing
  • Real-Time Market Insights
  • Wallet Stats (PnL, Net Worth, etc.)
  • Wallet Transaction Activity

And so much more.

The dashboard contains helpful links related to EVM chains (blockchains compatible with Ethereum) as well as information related to popular EIP protocols.

Feel free to visit the official Ethereum dashboard website and explore these features yourself!

Code Overview

In this section, we will briefly cover efficient data fetching using the SWR library and graphing using the Recharts library.

You can follow along by cloning the following repository. The directory of concern is src/app.

Official documentation related to the repo can be found here.

The SWR library is similar to @tanstack/react-query which allows users to efficiently fetch, cache, and re-validate data.

The library comes with its own custom hook, useSWR which can only be used in client components (React hooks only work with client components).

You can perform data fetching using the Fetch API for server components and often times, that approach is probably the best (you get data fetching and the benefits of working with server components).

The official docs dive deeper into useSWR, but we will focus on three key parameters:

  • Key — Uniquely identify a particular request for caching
  • Fetcher function — Function used to perform the data fetching request
  • Options — Object that consists of properties such as refresh interval, etc.

When working with coin pricing, we wrap the client component that makes use of this hook with a server component.

An example of this can be found below.

We display information related to coin pricing and take advantage of Next’s dynamic file routing:
/src/app/prices/[coin]/page.tsx:

GitHub GistTSX
import GenericChartPage from "@/app/components/GenericChartPage";
import { coinValidator } from "@/app/utils/functions/coinValidator";
import CoinChartInfoType from "@/app/utils/types/CoinChartInfoType";
import type { Metadata } from "next"

// Custom Metadata for SEO
export const metadata: Metadata = {
    title: "Cryptocurrency Price",
    description: "Analyze a cryptocurrency based on recent market data"
}

// Displaying historical price information of a particular coin
export default async function CoinPriceInformationPage({ params }: { params: Promise<{ coin: string }>}) {
    const coinID = (await params).coin;

    // Check validity of this coin by running a custom function validating if it exists within the Coin Gecko coin list
    const validateCoin: CoinChartInfoType = await coinValidator(coinID);

    if (validateCoin) {
        // Render the Generic Chart Page componen if the coin ID is valid
        return (
            <div className="p-4 bg-gray-900 shadow-lg">
                <GenericChartPage 
                    data={{
                        id: coinID,
                        name: validateCoin.name, 
                        symbol: validateCoin.symbol.toUpperCase(), 
                        market_data: {
                            current_price: validateCoin.market_data.current_price,
                            price_change_percentage_24h: validateCoin.market_data.price_change_percentage_24h
                        }
                    }} 
                />
            </div>
        )
    }   
    else {
        // Coin ID is not valid, therefore return the error page
        throw new Error();
    }
}
Server component for wrapping the client component using SWR

We utilize a custom coinValidator function to check the validity of the coin ID being passed in prior to rendering the client component.

The GenericChartPage is the client component where the SWR library is being used
/src/app/components/GenericChartPage.tsx:

GitHub GistTSX
'use client';

import { useState } from 'react';
import useSWR from 'swr';
import PostFetcher from '../utils/functions/PostFetcher';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../components/ui/select";
import CoinChartInfoType from '../utils/types/CoinChartInfoType';
import priceFormatValidator from '../utils/functions/priceFormatValidator';
import PostFetcherArgumentsType from '../utils/types/PostFetcherArgumentsType';

// Generic Chart Page Custom Component
export default function GenericChartPage(props: { data: CoinChartInfoType }) {
    const { data } = props;
    const [interval, setInterval] = useState<string>('7');

    // Fetch data for chart display
    const { data: coinChartData, error: coinChartError, isLoading: coinChartDataLoading } = 
    useSWR(['/api/coin-prices-by-day', { coin: data.id, interval }], ([url, body]: [string, PostFetcherArgumentsType]) => PostFetcher(url, { arg: body }), { refreshInterval: 50000 });

    // Conditionally render data
    if (coinChartError) {
        return <div><p className='text-white-500'>Error Loading Market Cap Chart Data...</p></div>
    }
    else if (coinChartDataLoading) {
        return <div><p className='text-white-100'>Loading Market Cap Chart Data...</p></div>
    }
    else {
        // Retrieve key information
        const chartData = coinChartData.coinPrices;

        // Modifying the y-axis domain for appropriate ranges
        const prices = coinChartData.coinPrices.map((item: { price: string }) => item.price);
        const min = Math.min(...prices);
        const max = Math.max(...prices);
        const buffer = (max - min) * 0.1; // 10% buffer
        const current_price = !priceFormatValidator(data.market_data.current_price.usd) ? " $" + data.market_data.current_price.usd : " $" + Number(data.market_data.current_price.usd).toFixed(2);
        
        // Render data based on market information
        return (
            <div className="bg-gray-800 text-gray-300 py-10 px-4 sm:px-6 lg:px-8 shadow-lg">
                <h1 className="text-5xl font-bold mb-6 text-center">
                    <span className="bg-clip-text text-transparent bg-gradient-to-r from-gray-400 to-gray-100">
                        Historical Price Information
                    </span>
                </h1>
                <Card className="w-full bg-gray-900 border-gray-700 mt-10">
                    <CardHeader>
                        <CardTitle className="text-xl text-gray-100">{ data.name }</CardTitle>
                        <CardDescription className="text-gray-100">
                            Current Price:<b>{current_price}</b>
                        </CardDescription>
                        <div className="flex items-center space-x-2">
                            <CardDescription className='text-gray-100'>
                                24 Hour % Change: 
                            </CardDescription>
                            <CardDescription className={Number(data.market_data.price_change_percentage_24h) < 0 ? 'text-red-500' : 'text-green-500'}>
                                <b>{Number(data.market_data.price_change_percentage_24h) >= 0 ? ' +' : ''}</b><b>{Number(data.market_data.price_change_percentage_24h).toFixed(2) + '%'}</b>
                            </CardDescription>
                        </div>
                        <Select value={interval} onValueChange={setInterval}>
                            <SelectTrigger className="w-[180px] bg-gray-700 text-gray-100">
                                <SelectValue placeholder="Select Interval" />
                            </SelectTrigger>
                            <SelectContent className="bg-gray-700 text-gray-100">
                                <SelectItem value="24">24 Hours</SelectItem>
                                <SelectItem value="7">7 Days</SelectItem>
                                <SelectItem value="14">15 Days</SelectItem>
                                <SelectItem value="30">30 Days</SelectItem>
                            </SelectContent>
                        </Select>
                    </CardHeader>
                    <CardContent>
                        <div className="h-[400px] w-full">
                            <ResponsiveContainer width="100%" height="100%">
                                <LineChart
                                    data={chartData}
                                    margin={{
                                        top: 20,
                                        right: 30,
                                        left: 80,
                                        bottom: 20
                                    }}
                                >
                                <CartesianGrid strokeDasharray="3 3" stroke="#444" />
                                <XAxis 
                                    dataKey="date" 
                                    stroke="#888" 
                                    tick={{fill: '#888'}}
                                    padding={{ left: 10, right: 10 }}
                                />
                                <YAxis 
                                    stroke="#888" 
                                    dataKey="price"
                                    tick={{fill: '#888'}}
                                    domain={[Math.max(0, min - buffer), max + buffer]}
                                    padding={{ top: 10, bottom: 10 }}
                                    tickFormatter={(value) => value.toLocaleString('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 })}
                                />
                                <Tooltip 
                                    contentStyle={{backgroundColor: '#333', border: 'none'}}
                                    labelStyle={{color: '#888'}}
                                    itemStyle={{color: '#fff'}}  
                                />
                                <Legend />
                                <Line 
                                    type="monotone" 
                                    dataKey="price" 
                                    stroke="#ff4136" 
                                    strokeWidth={2}
                                    dot={true}
                                    name="Price (USD)"
                                />
                                </LineChart>
                            </ResponsiveContainer>
                        </div>
                    </CardContent>
                </Card>
            </div>
        )
    }
}
Generic Chart page for fetching and displaying coin price data

The component fetches data using useSWR and renders the relevant information using the Recharts library.

Since SWR is optimized for Next.js applications, we pass in the route from where we want to query data from /api/....

We pass in the coin ID as well as the interval in the useSWR hook for charting purposes.

As seen in Next.js tutorial, we make use of a fetcher function that specifically handles POST requests (PostFetcher) and pass this in as a parameter to the useSWR hook.

The back-end route used for making the request is the following
/api/coin-prices-by-day/route.ts:

GitHub GistTypeScript
import { NextResponse } from "next/server";
import dayjs from 'dayjs';

const PRO_COINGECKO_URL = "https://pro-api.coingecko.com/api/v3"; // Pro CoinGecko API Endpoint

// Custom Route Handler function
export async function POST(request: Request){
    const body = await request.json(); // Retrieve information from request
    const { coin, interval } = body;

    // Request coin prices
    let COIN_PRICE_ENDPOINT = '/coins/' + coin;

    if (interval === '24') {
        COIN_PRICE_ENDPOINT += '/market_chart?vs_currency=usd&days=2';
    }
    else if (interval === '7') {
        COIN_PRICE_ENDPOINT += '/market_chart?vs_currency=usd&days=7&interval=daily';
    }
    else if (interval === '14') {
        COIN_PRICE_ENDPOINT += '/market_chart?vs_currency=usd&days=14&interval=daily';
    }
    else {
        COIN_PRICE_ENDPOINT += '/market_chart?vs_currency=usd&days=30&interval=daily';
    }

    // Setting options for authenticated API call
    const options = {
        method: "GET",
        headers : {
            'content-type' : 'application/json',
            'access-control-allow-origin': '*',
            'x-cg-pro-api-key' : process.env.COINGECKO_GENERIC_API_KEY // API-KEY for authenticated call
        } as HeadersInit
    }

    // Fetch data based on request parameters
    const response = await fetch(PRO_COINGECKO_URL + COIN_PRICE_ENDPOINT, options);

    if (!response.ok) {
        return NextResponse.json({
            message: "Could not fetch coin price duration data"
        });
    }
    else {
        const information = await response.json();
        const prices: [[number, number]] = information.prices;
        
        // Conditionally send the response and format it conforming to the interval
        // Incorporate the dayjs library for easy date formatting
        if (interval === '24'){
            return NextResponse.json({
                coinPrices: prices.map(price => ({ 
                    date: dayjs(price[0]).format('YYYY-MM-DD HH:mm:ss').split(" ")[1], 
                    price: Number(Number(price[1])) 
                })).splice(24) 
            });
        }
        else {
            return NextResponse.json({
                coinPrices: prices.map(price => ({ 
                    date: dayjs(price[0]).format('YYYY-MM-DD'), 
                    price: Number(Number(price[1])) 
                }))
            });
        }
    }
}
Utilizing the paid CoinGecko API version and the dayjs library for efficient date handling

The response returns an object that maps the price to an object that consists of the time stamp and the price.

We pass this data to the GenericChartPage component and map the range of the y-axis using Math functions such as min/max.

The Recharts library is an effective charting library for working with React.js. It supports common charts such as Bar, Line, Pie, and so on.

It contains helpful components for tooltips, axis and legend configuration, and so much more.

It comes with its own responsive container with which you can wrap your graph component and each component consists of its own built-in props so all you have to do is plug and play.

More information on Recharts can be found be viewing their official docs here.

Demo Time!

If you want to run the website locally, you will need to read up this official README.md file related to the project.

For the demo, we will use the official site link to walk through some of the key features we discussed earlier.

Here is what the home page looks like:

No Image Found
Home page consisting of the main wallet search form
No Image Found
In the middle frame, you will find general market data as well as real-time information on market cap
No Image Found
Lastly, you will find information tables related to trending coins and collections

The home page is detailed, clean, and easy to navigate. Here, you see the Recharts library in action as that is what we are using to chart data related to the global cryptocurrency market cap.

Upon completing the form, you should see information related to the particular wallet.

The following screen details wallet transaction activity:

No Image Found
Wallet information displayed in the form of information tables

As seen from the browser URL, we are using dynamic routing and utilizing the wallet address passed into the search request to fetch the relevant data.

We are also using the SWR library. As the page loads to mount components, we are running requests to the back-end to fetch data and conditionally render the information tables.

Navigating to Prices > Coin Prices in the navigation bar should yield the following:

No Image Found
Deep dive into cryptocurrency prices (winning, losing, market cap)
No Image Found
Filter coins by searching them in the table

Again, the information tables are neatly detailed and easy to navigate. For each of these coins, you can select them to view more information about their pricing history.

For example, if you select Bitcoin in the Top 100 table, you should see the following:

No Image Found
Historical price information related to Bitcoin (BTC)

We capture the coin ID from the dynamic route slug (from the CoinGecko API request) and utilize it to fetch more information related to Bitcoin.

In this case, we use the SWR library to fetch historical price information of Bitcoin and use the Recharts library to chart this data as the component mounts.

Now let us revisit the often discussed section, market insights.

You can navigate to market insights by selecting Extra Data > Market Insights:

No Image Found
Market insights generated using AI in real-time

As seen in the previous tutorial, we are making use of the Firecrawl API and Vercel’s AI SDK to generate these market insights.

Finally, for helpful information related to different Ethereum protocols, you can navigate to that page by selecting Extra Data > EIP Protocols:

No Image Found
EIP protocols page detailing common protocols related to Ethereum

The remaining sections of the dashboard are simply forms which you fill out to view information. Feel free to explore those sections.

That is all as it relates to the demo of the Ethereum dashboard site. The design was inspired by the v0.dev agent.

Originally, this site was built using a combination of React.js and Node.js/Express. It was fully deployed and hosted on AWS using services such as Amplify (front-end) and EC2 (back-end).

I recently upgraded the dashboard and decided to migrate it using Next.js. By doing this, not only did I enhance user experience with additional features, but I also reduced cloud costs to $0.

Conclusion

All in all, we extensively covered the development of the Ethereum dashboard, a passion project of mine.

We built on the knowledge of the previous two tutorials and did a walk through of the key components that make up the Ethereum ecosystem.

We saw how the dashboard allows users to view data related to these key components and explored the Recharts and SWR libraries for graphing and efficient data fetching.

In the list below, you will find the links to the official GitHub repository used to build this project as well as links to the official Ethereum dashboard site, Recharts library, and the SWR library.

I am always open to making adjustments and improvements to the dashboard so feel free to like and drop a comment, it is always appreciated.

I hope you found this tutorial informative and that it inspires you to use Next.js for your future web projects.

Thank you!

No Name

Abdullah Muhammad

Blogger. Software Engineer. Designer.

Subscribe to the newsletter

Get new articles, code samples, and project updates delivered straight to your inbox.