IPFS with React: Loading IPFS Assets from Your Distributed IPFS Gateways

Ross Bulat
Dec 14, 2018 · 12 min read

IPFS, a distributed filesystem, offers a more decentralised way of storing and delivering web assets. This article explores how to integrate IPFS with React by fetching content from an IPFS gateway and loading those assets into React. To make the experience streamlined, the assets will be lazy loaded and faded in. But before we do this, your IPFS gateway and hosting VPS needs to be configured correctly. We will cover how to do this.

If you have not explored IPFS but wish to deploy it in alongside your React apps, then read my introduction on IPFS before continuing this article:

The above article focuses on setting up IPFS nodes on your own VPS, and then configuring a gateway to serve IPFS hosted assets. It acts as a necessary fundamental introductory to IPFS; it will get your nodes set up quickly and efficiently.

We will build upon this setup, and make our IPFS nodes serve assets more securely. Particularly, what we will cover is:

  • Setting up and NginX proxy pass to direct visits from http://<your_ip_address>/ipfs to your IPFS gateway. By doing this we are no longer required to include port 8080 (or whatever port your gateway is configured to).
  • Configuring NginX with SSL and domain name. Here we will further enahnce the NginX configuration, by adding a domain name, SSL and enable GZIP. At this point we’d have the ability to access IPFS content with the format http://<your_domain>/ipfs/<hash_to_content>.
  • CORS control with IPFS, to only allow your domains to access your IPFS node gateway.
  • Loading IPFS assets in React. From here, loading IPFS assets such as images and SVGs is very straight forward; all we need is the URL with an IPFS content hash. To ease the content in we will be using React LazyLoad and a React GSAP wrapper to animate the assets in once they are downloaded.
  • Choosing which IPFS Gateway to get content from. This is important for minimising latency. If you had 5 IPFS nodes running at different parts of the world, you’d want to get content from the nearest one depending on your visitors’ location. We will explore how to do this via 2 methods; React environment variables, and a server-side request to determine the nearest node.

With this in mind, let’s continue from where the first article left off — a newly installed IPFS node with its gateway enabled.

Firstly, NginX needs to be installed and configured in order to set up a proxy pass, to ultimately simplify the public URL to the gateway and to enable delivery over HTTPS. Let’s do that first.

Install NginX and Configure Proxy Pass

Our first port of call is to set up NginX on our IPFS node server. Depending on which OS you’re using, installation may differ. For an Ubuntu distribution, install NginX with the following:

#install nginx
sudo apt update
sudo apt install nginx
sudo systemctl status nginx
#open firewall ports
sudo ufw allow 'Nginx Full'
sudo ufw status

Now, let’s configure a proxy pass that will proxy every HTTP visit (port 80) to port 8080 — our IPFS gateway port. Create a default.conf file in /etc/nginx/sites-available:

server {
listen 80;
listen [::]:80;
server_name <your_ip_address>; #IPFS proxypass
location /ipfs {
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080$uri;
}
}

Be sure to replace <your_ip_address> with the public IP address of your server.

Now, if we restart NginX, you will be able to load IPFS assets directly from HTTP requests.

sudo service nginx restart

To test your configuration, check to see if the following kitten image loads, again placing your IP address in the URL:

http://<your_ip_address>/ipfs/QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ/cat.jpg

Let’s now add a domain name, SSL and GZIP compression to further secure and optimise IPFS asset delivery.

Adding SSL and a Domain (and GZip)

If you are familiar with NginX these configuration options will be very straight forward; another server block for port 443 is needed to be configured with the necessary options to allow SSL.

Note: Be sure to register an SSL certificate at this time if you have not already. You will need your certificate key and ca-bundle (with your crt included) for NginX to refer to.

We want to exclusively serve content over an encrypted connection, so we will want to either replace the above port 80 server with a port 443 server entirely, or amend our port 80 configuration to a permanent redirect to the HTTPS address of the same URI.

With this permanent redirect, all HTTP traffic will be redirected to the HTTPS URL of the same URI after the domain name

Amend the port 80 block like so:

server {    listen 80;
listen [::]:80;
server_name <your_domain> www.<your_domain>;
return 301 https://<your_domain>$request_uri;
}

Now we can be assured that only HTTPS requests will be processed.

Now, add the following HTTPS configuration under the port 80 server block, replacing the bolded text with your own values:

server {
listen 443;
listen [::]:443;
#configure SSL ssl on; ssl_certificate /etc/nginx/ssl/domain.ca-bundle;
ssl_certificate_key /etc/nginx/ssl/domain.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
resolver 127.0.0.1;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/domain.ca-bundle;
server_name <your_domain> www.<your_domain>; #configure GZIP gzip on;
gzip_vary on;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml image/gif image/jpeg image/png image/tiff image/vnd.wap.wbmp image/x-icon image/x-jng image/x-ms-bmp image/svg+xml image/webp
gzip_disable "msie6";
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.0;
#configure IPFS proxypass location /ipfs {
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080$uri;
}
}

This server block consists of our SSL and Gzip configuration — where we are compressing the majority of file formats supported — followed by the proxy pass itself to our IPFS gateway.

To learn more about the SSL configuration options NginX provides, refer to its SSL documentation:

Gzip is also configurable. The following NginX document introduces Gzip along with some recommended configurations:

The Proxy Pass

The last section of our server block is the IPFS proxy pass itself.

Some additional headers are set so we can access information about the request server. Perhaps the most important configuration is the proxy_pass value, pointing to localhost (127.0.0.1, port 8080), followed by the URI. The URI in this case will always begin with /ipfs/, following the same convention IPFS expects when requesting assets from a gateway.

Let’s restart NginX once again to reflect the recent changes:

sudo service nginx restart

Just like we did before, attempt to load the kitten image — now being served encrypted over HTTPS — with the following URL:

https://<your_domain>/ipfs/QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ/cat.jpg

With SSL now set up, we can move forward to only accept requests from our particular domain name, with CORS control.

Adding CORS Control to IPFS

Now, we only wish our domain to have access to our IPFS gateway — CORS has to be configured. This can be done in the command line. Run the following IPFS commands to limit domain access:

ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["<your_domain>"]'ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "GET", "POST"]'ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials '["true"]'

What these commands actually do is amend the API section of your IPFS config file. The content added to it by the above commands are as follows:

{
"API": {
"HTTPHeaders": {
"Access-Control-Allow-Credentials": [
"true"
],
"Access-Control-Allow-Methods": [
"PUT",
"GET",
"POST"
],
"Access-Control-Allow-Origin": [
"<your_domain>"
]
}
},
...

Using both IPFS commands or manually editing the IPFS config file provide us with the same outcome.

Restart IPFS for these changes to take effect

If you followed the previous article, we set up IPFS to be run in the background via supervisord. In this case, restart your ipfs process with the following:

sudo supervisorctl restart ipfs

In the case you are using a different process manager, you will have a similar capability to restart your process.

Now our domain is ready to serve IPFS assets directly from HTTPS requests. Let’s now explore how we can fetch some assets in React, and display them in an aesthetically pleasing way.

Loading IPFS Assets in React

At this point you can embed IPFS assets directly in your React apps. You could for example host the kitten image like so in an <img /> JSX tag:

<img src="https://<your_domain>/ipfs/QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ/cat.jpg" />

This will indeed load the image, but there are two issues you may wish to address:

  1. The loading of the image will not be immediate. The image will instead pop into the UI once it has been downloaded. This is of course the case with any asset that you load outside of your imported React resources.
  2. The image will begin to load immediately on page load — wouldn’t it be good if we could only load the image if it is present on the screen, or if we are scrolling down the page and are about to visit this image? After all, it is a rather big file, and will trigger another request as the page is loading when it may not be necessary.

We can solve these problems by lazy loading, and adding animation to introduce the loaded IPFS elements. We will utilise 2 packages here:

  • react-lazy-load: A simple way to stop assets loading until they are showing on screen, or when they are about to show. We will wrap a <LazyLoad /> component around our IPFS assets so they follow this behaviour.
  • react-gsap: A relatively new package that wraps the GSAP library into easy to use React components. By utilising the <Tween /> and <Timeline /> components we can introduce an animation (or staggered animation in a list of objects) to introduce assets.

Below, react-lazy-load and react-gsap will both be used simultaneously to achieve some nice behaviour when loading IPFS assets.

Consider the following example, where we simply predefine a list of IPFS URIs and load them in a list. To do this we map this list and wrap the IPFS asset inside a <LazyLoad /> component. Also within the <LazyLoad /> component are the <Tween /> and <Timeline /> components to control how the asset is animated into the page:

Check out this behaviour before continuing. This boilerplate can also be expanded, to include a request to a backend service to generate a list of IPFS assets for example.

Where to Fetch IPFS Assets?

The last issue we will tackle in this article is to configure which IPFS gateway to fetch assets from. If you are running a single IPFS instance and a single React app instance, the following discussion may not apply to you.

An application designed for a global audience should be distributed across multiple VPSs in strategic locations across the globe. Serving IPFS assets over a HTTP gateway is no different.

What is needed are IPFS gateways distributed in strategic locations to minimise latency.

App -> Gateway -> IPFS Delivery -> App

Content delivery within the underlying IPFS network is extremely fast. If a front end app was directly interacting with an IPFS node to fetch IPFS content, we wouldn’t need to worry about distributing our gateways — the underlying protocols would make sure the content I am requesting is delivered quickly and cached within many peers, so long as the content remains in demand. This is possible with other IPFS Javascript protocols, but is out of the scope of this article.

But what we are dealing with here is a scenario that our React app is making a request over HTTP to a gateway run on (most likely) another server. An additional HTTP request is being made to the IPFS gateway server before the network can work on delivering the requested asset. This communication between your app and the gateway server will cause additional latency which can be minimised if the IPFS hosting servers are strategically located.

Each React build needs to be aware where the nearest IPFS node is, in the form of its IP address. We can tackle this issue in at least 2 ways:

Method 1: Environment variables

Upon building a React app for a particular VPS, we can include the domain name of the nearest IPFS gateway server in the form of an environment variable. This method is fast and reliable, and no backend service requests are necessary to gather this information.

If you are using Create React App, environment variables can be defined in the root directory of your project. All environment variables must by prefixed with REACT_ for them to be considered at build time.

Store the IPFS gateway address as an environment variable like so:

  • Create an .env file in your React app directory if one does not already exist
  • Insert your variable containing the IPFS gateway URL:
REACT_IPFS_GATEWAY_URL = https://<your_domain>/ipfs/
  • Save the file

Now you can access the variable within the project via process.env.REACT_IPFS_GATEWAY_URL. Refering to the previous gist, loading an IPFS resource in an <img /> JSX object will now look like this:

<img src={`${process.env.REACT_IPFS_GATEWAY_URL}${record}`} />

We are simply combining the gateway URL followed by our hash URL, in this case mapped from our array.

You may wish to hardcode a resource, in which case we can use the same format:

<img src={`${process.env.REACT_IPFS_GATEWAY_URL}QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ/cat.jpg`} />

Build your React project and your .env variables will be available within the build, and consequently everywhere you push your build.

Method 1: Backend Service Request

Perhaps a more convenient approach if your IPFS gateways are changing frequently, you could also ask a backend node server, or other service, for the nearest IPFS resource.

This gives us additional flexibility, but more latency and an additional loading state on the front end; an additional request needs to be made to the server before IPFS assets can be loaded. Of course, if you are already relying on an initial backend request to load content, your IPFS gateway URL can be returned with this existing request.

What gives us the additional flexibility is that, with this method, we do not need to have knowledge of where exactly our React app is being hosted, nor where the IPFS gateway is being located.

Instead, our backend request could simply refer to the IP addresses only, and determine the location of the servers using a service such as GeoIP Lite, which I discussed in this article.

The Haversine Formula

From here, you could calculate the distance between each IPFS gateway server, and the React app, and return the URL of the nearest IPFS gateway.

Check out this experiment by Andrew Hedges that calculates the distance between 2 points on the earth based on longitude and latitude.

Given lon1 and lat1 of IP address1 and lon2 and lat2 of IP address 2, the formula boils down to the following Haversine Formula:

on = lon2 - lon1 
dlat = lat2 - lat1
a = (sin(dlat/2))^2 + cos(lat1) * cos(lat2) * (sin(dlon/2))^2
c = 2 * atan2( sqrt(a), sqrt(1-a) )
d = R * c (where R is the radius of the Earth)

Note: this formula does not take into account the non-spheroidal (ellipsoidal) shape of the Earth. It will tend to overestimate trans-polar distances and underestimate trans-equatorial distances. The values used for the radius of the Earth (3961 miles & 6373 km) are optimized for locations around 39 degrees from the equator (roughly the Latitude of Washington, DC, USA).

In any case, this solution will indeed determine the nearest IPFS node in an open sourced manor.

Conclusion

This article boiled down the process of configuring an IPFS gateway to deliver encrypted content directly from the IPFS network.

Once the secure delivery mechanism is in place, our front end apps are free to then request assets over HTTPS, loading them directly into the app. We attempted to load them in an aesthetically pleasing way by animating them into the scene, as well as limiting requests to our IPFS gateway with lazy loading.

Finally, we explored ways we can request content from the nearest IPFS gateway from the front end server in the event that our app is distributed.

Further Considerations

What we did not discuss in this article is IPNS or DNS Link, features of IPFS that are used to refer to a particular asset that is frequently updated. These features stem from IPFSs hashing algorithms whereby one change to a file will result in a different IPFS hash, breaking the original link. IPNS was introduced to solve this problem, giving us a means to point to the same resource even as its hash changes through modifications to the file.

These features and more will be discussed in a future article to further enhance your IPFS gateway delivery methods.

Ross Bulat

Written by

Author and programmer. Director @ JKRBInvestments.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade