Hosting a website on the IPFS protocol (rather than HTTP) ensures that your website will be secure and always available. Rather than fetching content from the website’s server, it can be fetched faster and more reliably from IPFS, regardless of where the content is stored. However, building on IPFS can be complicated. Unlike regular websites that are rendered in a server, IPFS can only host static generated websites, requiring a decent amount of development experience to build all pages and code before publishing.
Today I want to help you build faster and learn a new skill, while still creating an amazing product. By the end of this 4-step tutorial, you’ll be able to use Pinata to build a fully functional website hosted on IPFS. And this website will be a production-ready web application, unlike the landing pages and hobby projects you’re used to seeing—simply by combining the power of Pinata and a good idea.
So you have a better idea of what you’re building, here is the result of this tutorial: https://cyan-realistic-heron-820.mypinata.cloud. Notice that the navigation works just fine, even after a page refresh.
Here are the steps we’ll follow to build your IPFS website:
- Create a React app and install dependencies
- Building a basic app with routing
- Building the project statically
- Uploading the project to IPFS
Note:
If you already have a working app and are looking for steps on configuring your app, steps one and two can be skipped. However, keep in mind the configuration provided in this tutorial works only with apps built with Create React App 5. If you’re interested in building with NextJs, you may run into problems when it comes to navigation when creating IPFS static sites. The exception would be building a landing page that doesn’t have any routing, but that's for another time.
Ready to start building? Let’s get into it.
1. Creating the app
First, let’s create a react app that is going to serve as the base for your website. We are going to use the Create React App in this tutorial, as it is the easiest way to get started.
Run these commands to begin:
npx create-react-app ipfs-site
cd ipfs-site
npm start
To create and verify that the React App is working, navigate to localhost:3000
in your browser. From here, you should be able to see the rotating react logo. It should look like this:
2. Building a basic app with routing
Let’s start here with installing some dependencies. These dependencies will help us build our platform faster, making it so we don’t have to build everything from scratch. We'll need react-router and the react MUI library to help our UI look good too.
npm install react-router-dom @mui/material @emotion/react @emotion/styled
Next, let's create some pages. You can label these pages as anything you’d like - they’ll be used to test the navigation of the app we’re building. In this example, we’ll create the three pages key to any new business: a home page, an about page, and a pricing page. Create a folder called pages inside your project's [src] directory, and put in it three files named [home.js] [about.js] and [pricing.js] with the following content:
// src/pages/home.js
import {Box, Typography} from "@mui/material";
export default function Home() {
return (
<Box sx={{p:2}}>
<Typography variant={"h2"}>
This is the Home page
</Typography>
</Box>
)
}
// src/pages/about.js
import {Box, Typography} from "@mui/material";
export default function About() {
return (
<Box sx={{p:2}}>
<Typography variant={"h2"}>
This is the About page
</Typography>
</Box>
)
}
// src/pages/pricing.js
import {Box, Typography} from "@mui/material";
export default function Pricing() {
return (
<Box sx={{p:2}}>
<Typography variant={"h2"}>
This is the Pricing page
</Typography>
</Box>
)
}
Now, let's replace the App content with the router. Replace the [App.js] content with the following code:
// src/App.js
import {Container} from "@mui/material";
import {Routes, Route} from "react-router-dom";
import Home from "./pages/home";
import About from "./pages/about";
import Pricing from "./pages/pricing";
function App() {
return (
<Container sx={{py: 2}}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="pricing" element={<Pricing />} />
</Routes>
</Container>
);
}
export default App;
Next, we'll need to add the HashRouter wrapper, the main configuration trick in this tutorial. The function of that router is to put a hash # after the main route of our website, so whenever we navigate to our website, we don't call a page refresh from the server. This step is vital as files served through IPFS don't have a server, so we want to avoid refreshing the page every time you change a route.
Let's edit index.js (or whatever root file you use as the initial point for your entire application). It should look like this:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { HashRouter } from "react-router-dom";
const root = ReactDOM.createRoot(
document.getElementById('root')
);
root.render(
<React.StrictMode>
<HashRouter>
<App />
</HashRouter>
</React.StrictMode>
);
Finally, navigate to localhost:3000/#/about
.
You should be able to see the content of your 'about' page, the same for 'pricing'. Next, we’ll let's add some navigation components so you can directly click and navigate. We are going to use three of the most common ways of using the react-router navigation - NavLink, Link, and a regular Button.
Modify your App.js code so it looks like this:
import {Button, Container, Paper} from "@mui/material";
import {Routes, Route, useNavigate, NavLink, Link} from "react-router-dom";
import Home from "./pages/home";
import About from "./pages/about";
import Pricing from "./pages/pricing";
function App() {
const navigate = useNavigate();
return (
<Container sx={{py: 2}}>
<Paper sx={{p: 2, display: 'flex', justifyContent: 'space-between'}}>
<NavLink to={"/about"}>About Us </NavLink>
<Link to={"/pricing"}>Pricing</Link>
<Button
variant={"contained"}
onClick={() => {navigate("/")}}
>
Home
</Button>
</Paper>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/pricing" element={<Pricing />} />
</Routes>
</Container>
);
}
export default App;
3. Building the project statically
Now, it's time to build the static project for production and get ready to deploy to IPFS. But before that, we need to add one last configuration to our project.
Add this to your [package.json] file:
"homepage": "."
This ensures that the navigation will work for files served from a static location.
Now, run the [npm run build]. This command will build your project to the /build folder by default.
You can now test out your static project!
Drag the index.html file from the /build folder and drop it into your browser tabs - It will then open the static version of your website.
Now, we have a website that has great navigation and was statically generated, so it can be served from IPFS.
4. Upload the project to IPFS
For this tutorial, we will use Pinata not only as our IPFS provider but also for their Dedicated Gateways. Pinata’s gateways are efficient, secure, and easy to use - making it ideal to serve content to the audience of your choice quickly and reliably.
First, log in or create an account on the Pinata website. Next, upload the /build folder that we created from the previous step and click ‘Add Files’, then select the ‘Folder’ option. You can name your folder however you like.
Once your folder is uploaded, select the ‘more’ options for that folder in the list, click on 'Set as gateway root', then choose a gateway from the list.
By this point, you should already have a gateway created automatically. If that's not the case, you can create a gateway from the gateways page in the platform.
Lastly, copy and paste the gateway's name into a browser tab, and voila! Your static site is now live and on IPFS!
If you followed this tutorial, it should look a little something like this. Try a complex routing scenario (like refreshing any page or typing the exact URL) and your static page will still work just fine.
That’s it - you have successfully created your own working web application! 🎉
Happy pinning!
Top comments (0)