Back to blog
How To Ship A Static Website To IPFS with Pinata
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 <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 <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 <Home />
component when the URL matches the root path, and two more routes for the "about" and "contact" paths, rendering the <About />
and <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.
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.
Link an ENS Domain
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 🤠