Destiner's Notes

Using Dynamic Data to Generate Astro Pages

13 February 2022

Astro makes it possible to generate a static site based on a set of Markdown pages, which is very helpful for blogs. However, Astro also allows creating a site based on changing data. It’s possible to make a static site based on dynamic data.

We will create a site that will pull cryptocurrency data from Coingecko API. This API provides access to thousands of crypto coins and has a generous free plan, which is perfect for our needs.

Start by initializing an Astro project:

npm init astro -- --template minimal

Now, let’s create a page. For simplicity, we will start with a regular static page and go from there.

touch "src/pages/coin.astro"

Let’s provide a layout for our page. Here, I also provide demo data that I pulled from Coingecko. We will learn how to pull that data dynamically in a second.

---
const symbol = 'ETH';
const name = 'Ethereum';
const price = 3169.66;
const rank = 2;
const date = new Date('2015-07-30');
const description =
  'Ethereum is a <a href="https://www.coingecko.com/en?category_id=29&view=market">smart contract platform</a> that enables developers to build tokens and decentralized applications (dapps). ETH is the native currency for the Ethereum platform and also works as the transaction fees to miners on the Ethereum network.';
---

<h1>{name} ({symbol})</h1>

<p><b>Price: </b>${price}</p>
<p><b>Market cap rank: </b>{rank}</p>
<p><b>Date: </b>{date.toString()}</p>

<p>{description}</p>

If you go to /coin in your browser, you should see this page rendered. Yet, so far the page is pretty static.

Once we have a working layout, we can start fetching data to fill it. To make requests, we will use axios package:

npm i -D axios

Now we can pull the data from Coingecko:

import axios from 'axios';

const client = axios.create({
  baseURL: 'https://api.coingecko.com/api/v3',
});

const coinResponse = await client.get('/coins/ethereum');

const symbol = coinResponse.data.symbol.toUpperCase();
const name = coinResponse.data.name;
const price = coinResponse.data.market_data.current_price.usd;
const rank = coinResponse.data.market_cap_rank;
const date = new Date(coinResponse.data.genesis_date);
const description = coinResponse.data.description.en;

Now we have a page that is updated every time you run the build (or reload if you’re in dev mode). But what if we want to add more coins? Maybe, hundreds of them. Do we have to create an Astro file for each page? Thankfully, no. We only need to create a single dynamic page.

Let’s make our page dynamic:

mv "src/pages/coin.astro" "src/pages/[coin].astro"

What that means is that Astro will generate pages with /:coin path, where coin is the id of the coin. You can read more about dynamic routes in Astro docs.

Now, we need to tell Astro what pages to generate. In other words, we need to provide a list of coins. Each element of that list will result in a generated page. We can use getStaticPaths built-in function for that:

// imports...

export function getStaticPaths() {
  return [
    {
      params: {
        coin: 'bitcoin',
      },
    },
    {
      params: {
        coin: 'ethereum',
      },
    },
    {
      params: {
        coin: 'usd-coin',
      },
    },
    {
      params: {
        coin: 'solana',
      },
    },
    {
      params: {
        coin: 'polkadot',
      },
    },
  ];
}

// data fetching...

Since we provided a list of 5 elements, Astro will generate 5 pages.

Tip: the list of coins can be dynamic itself. For example, you can use /coins/list Coingecko endpoint to fetch the list of all supported crypto coins and pass that into getStaticPaths. That way, Astro will generate a page for each coin listed on Coingecko. Beware, there are thousands of coins!

For example, you can open /bitcoin to see that the page was indeed generated. However, you’ll notice that it still has data for Ethereum. How so? Well, we need to tell Astro to use the id of the current coin when pulling the data.

const { coin } = Astro.params;
const coinResponse = await client.get(`/coins/${coin}`);

Now, when you go to /bitcoin, /ethereum, or any other coin page, you’ll see that it has the correct data.

Here’s the full source of our dynamic coin page:

---
import axios from 'axios';

export function getStaticPaths() {
  return [
    {
      params: {
        coin: 'bitcoin',
      },
    },
    {
      params: {
        coin: 'ethereum',
      },
    },
    {
      params: {
        coin: 'usd-coin',
      },
    },
    {
      params: {
        coin: 'solana',
      },
    },
    {
      params: {
        coin: 'polkadot',
      },
    },
  ];
}

const client = axios.create({
  baseURL: 'https://api.coingecko.com/api/v3',
});

const { coin } = Astro.params;
const coinResponse = await client.get(`/coins/${coin}`);

const symbol = coinResponse.data.symbol.toUpperCase();
const name = coinResponse.data.name;
const price = coinResponse.data.market_data.current_price.usd;
const rank = coinResponse.data.market_cap_rank;
const date = new Date(coinResponse.data.genesis_date);
const description = coinResponse.data.description.en;
---

<h1>{name} ({symbol})</h1>

<p><b>Price: </b>${price}</p>
<p><b>Market cap rank: </b>{rank}</p>
<p><b>Date: </b>{date.toString()}</p>

<p>{description}</p>

That way, we got a list of pages that are updated each time you trigger a new build. We build this site the same way we build a “normal” Astro site:

npm run build

The obvious caveat of this setup is that the pages won’t be updated automatically when the underlying data is updated. That’s the way static sites work! However, you can trigger a new build manually when you need it, and the site will update. Or better, you can set up a cron job to run the build automatically, e.g. each day at 8 AM.