Blog home

How To Ship A Static Website To IPFS with Pinata

Pinata

Published on

8 min read

How To Ship A Static Website To IPFS with Pinata

Bringing the ethos of decentralization into website development.

The idea of what a website can be has evolved. They still represent the very essence of the internet; gateways to information, platforms for communication and windows into the digital universe. But the possibilities and the ideas behind the safety, security and distribution of websites have evolved rapidly in recent years. And we have decentralization to thank.

Taking the leap from a centralized server to decentralizing your website’s home means that YOU have full control over whether your website persists—not a centralized agent who does not care if your creations live or die.

For many of us builders, creating and deploying a website is often one of the first steps we take in our career. In this written tutorial, we bring the ethos of decentralization into website development and take you through the process of deploying your static website to IPFS via Pinata.

Prerequisites

  • Code editor (e.g. VS code)
  • Pinata account and IPFS Gateway
  • Vite installed
  • npm

If you’re new to this space, I highly recommend in opening these articles in a new tab to get up to speed as you go through this tutorial: What is IPFS? What is an IPFS Gateway?

🏁 Start: Creating your Website Base

Ready to start building? Let’s get into it.

Start by grabbing the clone repository from our Github:

git clone https://github.com/kk-im/pinata-website-boilerplate

Project Architecture

In the root of your project, you should find a project folder, and the following pages:

Home.jsx

export default function Home() {
  return (
    <div className="p-5 items-center">
    </div>
  )
}

About.jsx

export default function About() {
  return (
    <div className="p-5 items-center">
    
    </div>
  )
}

Contact.jsx

export default function Contact() {
  return (
    <div className="p-5 items-center">
    </div>
  )
}

For each page, put in a h1 of your choice so as to make them distinguishable e.g. “This is my _____ page”. Feel free to be as creative as you wish! This is your website 😇

Let’s go ahead and import those into our App.jsx file.

import './App.css'

// Pages
import Home from './pages/Home'
import About from './pages/About'
import Contact from './pages/Contact'

// Layout


function App() {

  return (
  
  )
}

export default App

Set up your Root component

In your project, you will have a folder called components with a single file inside: Root.jsx.

This Root component is important as it serves as the central point of configuration and organization for routing within our application. In a nutshell, it keeps all our code clean, maintainable, and reusable.

Create your navigation bar

The Root.jsx component, is going to be where our website navigation bar lives.

Under the opening &lt;div>, write in this chunk of code:

<header className="w-100">
   <nav className="flex justify-between p-4 items-center flex-row top-0">
      <h1 className="text-lg font-bold">My Pinata Website</h1>
         <div>
           <NavLink className="mr-5" to="/">Home</NavLink>
           <NavLink className="mr-5" to="/about">About</NavLink>
           <NavLink to="/contact">Contact</NavLink>
	       </div>
    </nav>
</header>

Nested inside the header is a nav element, which contains our page title, and page links.

If NavLink is not automatically imported from react-router-dom, import it manually at the top of your file:

import { NavLink } from "react-router-dom";

Set up Hash Routing with React-Router-Dom v6

Now, we configure our routing! 📍

In your App.jsx file, under the “// Layout” comment, import the Root component.

import Root from './components/Root'

Underneath the import, you’ll paste in our routing configuration:

const router = createHashRouter(
  createRoutesFromElements(
    <Route path="/" element={<Root />}>
      <Route index element={<Home />} />
      <Route path="about" element={<About />} />
      <Route path="contact" element={<Contact />} />
    </Route>
  )
)

Ok! Now this is important. Let me take you through this line-by-line, as this is the key part of the magic of this project. You can trust me.

We use the createHashRouter function to create an instance of a router that utilizes hash-based URLs.

💁‍Hash-based routing is not so common, but preferred when building static websites.

Below that, we use the createRoutesFromElements function to convert the nested JSX elements below into route configuration objects. This function is import as it helps simplify the process of defining routes by allowing us to write JSX syntax (instead of manually creating route objects).

Within this route configuration, we define the root route ("/") with the Route component, and assign it the &lt;Root /> component as its child element. This ensures that the Root component will be rendered for all nested routes within it.

Inside the Root component, we define three additional routes using the Route component: an index route (denoted by index) that renders the &lt;Home /> component when the URL matches the root path, and two more routes for the "about" and "contact" paths, rendering the &lt;About /> and &lt;Contact /> components, respectively.

By defining the routes in this hierarchical manner, we establish the navigation structure and component associations within our application. When a user navigates to a specific URL, React Router will match the corresponding route and render the appropriate component.

This approach allows for a modular and declarative way of defining routes, making it easier to manage and scale the routing configuration of our React application.

And that’s it! Well done — you’ve conquered the trickiest part of this project.

Another finicky addition — make the App function return:

<RouterProvider router={router} />

Lastly, make sure you have the required imports at the top of your code:

import { createHashRouter, Route, createRoutesFromElements, RouterProvider } from 'react-router-dom'

Import Outlet into Root.jsx

We’re nearly at the end!

Navigate back to Root.jsx.

We’re going to import the Outlet component and use it as a placeholder for the content of the current route. In other words, it allows for a dynamic rendered of components of the current route.

Under your header element, you’re going to nest Outlet inside a main element.

Your final code for Root.jsx should look like this:

import { NavLink, Outlet } from "react-router-dom";

export default function Root() {
  return (
    <div className="h-full">
        <header className="w-100">
            <nav className="flex justify-between p-4 items-center flex-row top-0">
                <h1 className="text-lg font-bold">My Pinata Website</h1>
                <div>
                    <NavLink className="mr-5" to="/">Home</NavLink>
                    <NavLink className="mr-5" to="/about">About</NavLink>
                    <NavLink to="/contact">Contact</NavLink>
                </div>
            </nav>
        </header>
        <main>
             <Outlet />
        </main>
    </div>
  )
}

Test your website

Sweet, ready to see the fruits of your labor? 🍎

Run npm run dev and navigate to your local port at http://localhost:5173/.

You’ll see a simple static website with dynamic routing, ready to deploy to IPFS 😎

Build it 🏗️

Now, run npm run build, to execute the build script. This step transforms and bundles up the source code of your application to a form that’s production ready 🕺🏼.

You’ll see a new folder called dist has been created, with an  index.html at the root.

Folder tree inside the code showing where to find assets

Ship it 🚢

We can now upload it to Pinata and deploy it to IPFS. We’ll even customize it with our dedicated gateway so it looks more like a proper website‍.

💁‍First time hearing about dedicated gateways? Read up on ‘em here and get excited 💖

If you have a Pinata account, log in to your dashboard. If you haven’t created one yet, no stress — take a quick 5 to sign up here.

From your dashboard, you want to click ‘Add file’, and select ‘Folder’. Click on ‘Select’, and find your project source code. Inside your project, highlight your entire dist folder, and click ‘Upload’.

Give the folder a name (like ‘my-first-pinata-website’) and wait for Pinata to pin it to IPFS. Once that’s done, you should be able to preview your website and it will look exactly as it did in your development server!

Set as Gateway Root

Click on the ‘More’ button on your file, and select ‘Set as gateway root’. You’ll be prompted to choose your dedicated gateway (you will already have one created as part of your sign up).

You should now be able to see your static website live if you pop your dedicated gateway in any browser!

Next Steps

Now that your website is built, there are just a few more steps to make it more shareable and delicious for the world.

Set Up a Custom Domain

Your current website lives at the address of your Pinata dedicated gateway, but you can migrate it to your very own custom domain too!

Check out this step-by-step resource from our Head of Community, Steve.

This one is for all those web3 native builders out there. ENS is our domain (literally).

If you want to take this tutorial one step further, head over to this tutorial on how to link your own ENS to your static website.

Create More

In this tutorial, you’ll successfully built, configured and shipped an entire static website to IPFS with Pinata. Did you know you can do the same with images, videos, entire applications and more? Upload to Pinata. Share to the world. The world is your oyster.

We’re here to help 💜

We’re happiest when our tools help you build your dreams. When you win — we win.

So hop into our Discord to show off your creations in our #powered-by-pinata channel, upload screengrabs or recordings and tag us on Twitter/IG/Threads, or even upload a demo on YouTube and tag us.

If you have any remaining questions, need another pair of eyes for your project, or just need some positive vibes — party with our team in our Discord.

Happy Pinning ya’ll 🤠

Stay up to date

Join our newsletter for the latest stories & product updates from the Pinata community.

No spam, notifications only about new products, updates and freebies. You can always unsubscribe.