Building a Static Website on Arweave

Created by:

About this lesson

Greetings! I welcome you to the first project lesson of the Arweave 101 track. In this lesson, you will test your knowledge of the previous lessons by hosting your first website on Arweave.

You will start by creating a website with Eleventy, a popular static site generator. Then, implement an upload script that leverages the Turbo SDK and Arweave-js for deployment and choose an easy-to-remember ArNS name.

Prerequisites

Understanding the topics of lessons 1 and lesson 2 is a must to start this lesson.

You need a basic understanding of web technologies like HTTPS, HTML, JavaScript, and Node.js.

A basic understanding of blockchains is helpful, too. At least you should understand what wallets and transactions are.

A browser, Node.js, and an ArConnect wallet to try the examples.

đź’ˇ

Note: The ArNS section of this lesson requires AR.IO test tokens (tIO). You can complete the whole lesson without the ArNS part, but the UX of your site might suffer from a missing ArNS name. You can get tIO through an ecosystem DEX or by completing an EXPerience quest. I will explain that process later in the ArNS section of this lesson.

Why a Static Website?

Arweave’s primary goal is to provide the world with permanent, immutable storage. Hosting static websites and single-page applications (SPAs) are great realizations of that mission. While Arweave allows the upload of arbitrary data, websites have a special place: they form the Permaweb—a decentralized, censorship-resistant version of the Web, without dead links. If you add the correct tags when uploading a website, gateway nodes will deliver them with text/html Content-Type and make them accessible via a browser.

The spirit of this course is to start from the simple concepts and then move to the more complex ones, and since a static website is easier to build than a SPA, you will begin with a static website. After mastering the basics, you’ll learn about SPAs in the next lesson.

Creating Your Website

Your first step is to create the website; in this case, you will create a blog with Eleventy. To do this, you must create a new Node.js project, install Eleventy, and add files that conform to Eleventy’s templating language.

Setting Up the Project

To create the website, you have to create a new Node.js project and install Eleventy with the following commands:

mkdir permablog && cd permablog
npm init -y
npm i -D arweave @11ty/eleventy @ardrive/turbo-sdk @ar.io/sdk

Create a .eleventy.js file to define a filter that drops the first slash. Eleventy will start all permalinks with a slash, but websites on Arweave are nested under their transaction ID (TXID), so going to /your-post/ doesn’t work. Arweave TXIDs are permanent, so that’s no issue on the Permaweb, but Eleventy doesn’t know this.

Add the following code to the config file:

module.exports = function (eleventyConfig) {
  eleventyConfig.addFilter("rel", (url) => url.replace(/^\//, ""));
};

This enables you to use {{ yourUrl | rel }} in the templates when you want to convert an absolute URL generated by Eleventy to a relative URL that works on the Permaweb.

By default, an Eleventy permalink defined as /your-post/ would point to this (non-existent) URL:

https://arweave.developerdao.com/your-post/

With the filter, it will point to this URL instead:

https://arweave.developerdao.com/some-transaction-id-9999/your-post/

Creating a Layout

Eleventy's sophisticated templating system allows you to reuse HTML code from layout files. So, your first step is to create a layout file containing the scaffold HTML for your blog, which Eleventy will wrap around all your content files.

Create a new file at src/_includes/layout.html. Eleventy processes HTML files with liquid templates, which enables you to render dynamic content and display elements conditionally. Eleventy will render the template you wrapped with this layout at the content. If the wrapped template contains a title in its data, Eleventy will render a link back to your home page.

Add the following code to the new file:

<!doctype html>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ title | default: "Permablog" }}</title>
<h1>{{ title | default: "Permablog" }}</h1>
{% if title %}
<a href="../">Articles</a>
{% endif %} {{ content }}

Creating the Pages

Next, create a new src/index.md file. This will become your blog’s home page, listing links to all blog posts. Like HTML, Markdown files are processed with liquid templates to allow dynamic content.

The front matter at the top of a template contains either data that Eleventy uses, for example, to find the right layout file, or data you can use inside the template.

The collection property contains an array of tags you will use in your templates later. Creating pages with a post tag will add them to the post collection and ensure they appear in the list rendered on your home page.

The post.url is added with the rel filter you defined above to ensure the URL doesn’t start with a slash; instead, it keeps the TXID in which your website is nested.

The pkg.author comes from author field of your project’s package.json file. So make sure you enter a name there!

Add this code to the new file:

---
layout: layout.html
---

<h2>Articles</h2>
<ul>
{% for post in collections.post %}
  <li><a href="{{ post.url | rel }}">{{ post.data.title }}</a></li>
{% endfor %}
</ul>
<p>by {{ pkg.author }}</p>

To prevent repetition, create a data file inside a directory that applies this front matter to all template files in that directory.

Create a new file at src/posts/posts.json to define data for all post files.

  • layout defines which file should wrap your post pages
  • tags define the tags of each post page; here, using post ensures every post will be part of the post-collection used on the home page.
  • permalink defines the URL schema for your pages; the schema ensures the file name without the extension is used as the post URL.

Add the following code to the JSON file:

{
  "layout": "layout.html",
  "tags": "post",
  "permalink": "/{{ page.fileSlug }}/"
}

Now, only the actual posts of your blog are missing, so let’s create a new file at src/posts/hello-world.md. This template will inherit the data from the posts.json file and add its own data as a title.

Add the following code to the template file:

---
title: Hello, World!
---

I deployed my first website to the Permaweb!

You can modify the first post or the layout to suit your blogging needs and add other pages to the src/posts directory. You can move to the build step when you’re happy with your work.

Building a Release

Add new commands to your scripts in the package.json file to generate HTML from your templates.

  • The start script runs a web server that lets you test your blog locally.
  • The build script generates the HTML and stores it in the dist directory.
"scripts": {
  "start": "npx @11ty/eleventy --serve --input=src --output=dist",
  "build": "npx @11ty/eleventy --input=src --output=dist"
},

Execute the build script with this command:

npm run build

The output should look like this:

> permablog@1.0.0 build
> npx @11ty/eleventy --input=src --output=dist

[11ty] Writing dist/index.html from ./src/index.md (liquid)
[11ty] Writing dist/hello-world/index.html from ./src/posts/hello-world.md (liquid)
[11ty] Wrote 2 files in 0.10 seconds (v2.0.1)

After completing this command, you’ll find your rendered HTML files in the dist directory.

Testing the Release Locally

If you want to check that everything with your website is working as you expect, run the following command:

npm start

The start script builds your website and starts a development server on http://localhost:8080/, where you can test your blog before deploying it.

Deploying Your Website to Arweave

Now that you have a website, you can move to the interesting part: deploying on Arweave.

To deploy your website, you’ll implement a script that uses Node.js, the Turbo SDK, and an Arweave wallet.

Setting Up a Wallet

If you completed lesson 2, you should already have a wallet created in ArConnect and exported as a keyfile. Copy it to your project directory and ensure it’s named key.json.

You can continue this lesson with a randomly generated keyfile, as all uploads are smaller than 100 KiB and are free with Turbo; however, using a wallet that is charged with Turbo Credits (explained in lesson 2) is recommended to ensure uploads are larger than 100 KiB.

If you want to continue without creating a charged wallet, create a new file at scripts/create-wallet.mjs with this code:

import fs from "node:fs";
import Arweave from "arweave";
const wallet = await new Arweave({}).wallets.generate();
fs.writeFileSync("key.json", JSON.stringify(wallet));

Then execute it with this command:

node scripts/create-wallet.mjs

When the command is complete, you’ll have a key.json file in your project directory.

Creating a Deployment Script

As you learned in lesson 2, you can upload files via the browser and Node.js. As deployments are often automated, it makes sense to implement it as a Node.js script.

Create a new file at scripts/deploy-website.mjs and add the following entry to scripts in your package.json file:

"deploy": "node scripts/deploy-website.mjs"

This script will collect all the files in the dist directory, load your key.json file, and upload each file with the Turbo SDK to Arweave. After it uploads all files, it will use its paths and TXIDs to generate a path manifest that allows users to access all files via a single TXID.

Add the following code to the deployment script:

import fs from "node:fs";
import path from "node:path";
import mimeTypes from "mime-types";
import * as TurboSdk from "@ardrive/turbo-sdk";
console.log("Deploying website...");
function getAllFiles(dir, allFiles = []) {
  fs.readdirSync(dir).forEach((file) => {
    const filePath = path.join(dir, file);
    if (!fs.statSync(filePath).isDirectory()) return allFiles.push(filePath);
    allFiles = getAllFiles(filePath, allFiles);
  });
  return allFiles;
}
const turbo = TurboSdk.TurboFactory.authenticated({
  privateKey: JSON.parse(fs.readFileSync("key.json", { encoding: "utf-8" })),
});
const uploadResults = [];
for (let filePath of getAllFiles("dist")) {
  console.log(`- ${filePath}`);
  const result = await turbo.uploadFile({
    fileStreamFactory: () => fs.createReadStream(filePath),
    fileSizeFactory: () => fs.statSync(filePath).size,
    dataItemOpts: {
      tags: [{ name: "Content-Type", value: mimeTypes.lookup(filePath) }],
    },
  });
  uploadResults.push({
    path: filePath
      .replaceAll(path.sep, "/")
      .replace("dist/", "")
      .replace("/index.html", "/"),
    txId: result.id,
  });
}
console.log("- manifest.json");
const pathManifest = {
  manifest: "arweave/paths",
  version: "0.2.0",
  index: { path: "index.html" },
  paths: uploadResults.reduce(
    (paths, file) => ({ ...paths, [file.path]: { id: file.txId } }),
    {},
  ),
};
fs.writeFileSync("manifest.json", JSON.stringify(pathManifest));
const result = await turbo.uploadFile({
  fileStreamFactory: () => fs.createReadStream("manifest.json"),
  fileSizeFactory: () => fs.statSync("manifest.json").size,
  dataItemOpts: {
    tags: [
      { name: "Content-Type", value: "application/x.arweave-manifest+json" },
    ],
  },
});
fs.writeFileSync("deployment-id", result.id);
console.log(`https://arweave.developerdao.com/${result.id}`);

Deploying the Release

Uploading your files to Arweave is just one command away:

npm run deploy

The output of this command should look like this:

> permablog@1.0.0 deploy
> node scripts/deploy-website.mjs

Deploying website...
- dist/hello-world/index.html
- dist/index.html
- manifest.json
https://arweave.developerdao.com/<TXID>

The deployment script will write a deployment-id file with the latest TXID to help find your latest deployments.

Congratulations!

You’ve deployed your first website to the Permaweb! If you stop now, you’ve already learned enough to build and deploy basic websites. However, if you move on, you’ll learn to improve the user experience and, in later lessons, how to build DApps!

Testing the Deployed Release

Testing is quite simple; you just need to open the printed URL in your browser. In the end, the TXID should work with all Arweave gateways. Figure 1 shows the blog in the browser.

Figure 1: Permablog preview

Figure 1: Permablog preview

You can try the blog I deployed with different gateways:

Creating a Human-Friendly Name For Your Website

Your blog is fully functional already, but working with the TXID can be cumbersome. They are nice for permanent links to your pages but not for driving new users to your website. Also, when you upload a new blog release, the pages from previous transactions won’t reflect the changes. This is where ArNS comes into play: The Arweave Name System.

What is the Arweave Name System?

ArNS is a decentralized naming system that allows you to rent or buy names for your TXIDs. In contrast to TXIDs, they are mutable, allowing UX closer to traditional websites.

Instead of the TXID URL that looks like this:

https://arweave.developerdao.com/some-transaction-id-9999/path/to/some/file.html
https://ar-io.dev/some-transaction-id-9999/path/to/some/file.html

You can share an ArNS URL that looks like this:

https://arweave101.arweave.developerddao.com/path/to/some/file.html
https://arweave101.ar-io.dev/path/to/some/file.html

When you upload a new release, you can simply point the ArNS name to the new TXID, and your users will automatically see the latest version.

ArNS supports so-called under names, which are additional names prefixed with an underline to your ArNS domain.

blog_arweave101.arweave.developerddao.com/path/to/file.html
test_arweave101.arweave.developerddao.com/path/to/file.html
first_test_arweave101.arweave.developerddao.com/path/to/file.html
second_test_arweave101.arweave.developerddao.com/path/to/file.html

Arweave Name Tokens (ANTs) are NFTs that enable the management of ArNS names in a decentralized way.

In the next step, we will walk you through registering an ArNS name. If you don't already have AR.IO test tokens then you can claim some by completing EXPerience quests.

Getting tIO Tokens

AR.IO Testnet Tokens (tIO) are required to register an ArNS name.

Users who completed our previous course, Lesson 3 - Storing Data on Arweave, AND completed the corresponding AR.IO EXPerience quest have been airdropped sufficent tIO tokens to register a 12-character ArNS name.

You can check for the tokens using your ARWEAVE wallets at https://arns.app/ .

If you did not complete this prerequisite, then do not worry - there will be opportunities to acquire tokens in the future - stay tuned!

Registering an ArNS Name

You can register an ArNS name on the ArNS website. Keep in mind that ArNS is currently in testnet.

First, click the "Connect" button at the top right, to connect your ArConnect wallet.

Then, search for your desired ArNS name. The tIO tokens from the quests should be enough to lease a 12 characters long name for 1 year.

When the name is available, you click the "Register Now" button to start the registration process.

Figure 2 shows how the registration form. Enter the TXID from the deployment-id file in your project directory into the "Arweave Transaction ID" field, to point your ArNS name to your latest release right after the registration completes.

Figure 2: Registering an ArNS name

Figure 2: Registering an ArNS name

Testing Deployed Release With the ArNS Name

After changing the TXID and your ArNS name, you can access your blog via your new ArNS name.

I created an example undername to illustrate the functionality. You can test them with different Arweave gateways:

https://demo-blog_fllstck.arweave.developerdao.com/
https://demo-blog_fllstck.ar-io.dev/

And that’s it!

You’re now ready to build and deploy any website on Arweave while ensuring that your users' URLs don’t change with each deployment.

Summary

In this lesson, you learned how to put your knowledge to the test by creating a static website, uploading it to Arweave, and registering an ArNS name for it. You used Node.js scripts to interact with ANTs to automate the update for your ArNS names after a website update. Now you can build websites for the Permaweb to save your users from link-rot and ensure they have access to your page as long as Arweave is online.

Connect your wallet and Sign in to start the quiz

Conclusion

Website hosting and registering human-friendly names works similar to the traditional web. Instead of a hoster, you use Arweave and instead of DNS, you use ArNS. In contrast to the traditional method, your website stays online even if you don’t pay your hosting providers subscription fee. If an Arweave gateway gets blocked or stops serving your website, your users can still access it via other gateways.

Congratulations!

You finished the first project lesson of the Arweave 101 track and can consider yourself now a Permaweb developer!

Your pages might just have basic functionality, but that’s already enough to build news sites, blogs, and landing pages.

In the next lesson, you will learn how to build an interactive DApp that lets users interact with Arweave from the browser.