33

I have created a Gatsby app and configured gatsby-node.js to a create client only paths, which are all working fine in development while directly accessing the url of the path but not in production.

example :

if(page.path.match(/^\/sample/)){
     page.matchPath = "/sample/:value1/:value2/:value3";
     createPage(page)
  }

I am using heroku to deploy the app

Mike
  • 4,041
  • 6
  • 20
  • 37

10 Answers10

59

The Why

While the client-side router knows about this path, there is no corresponding HTML file. When the browser looks at the site it first loads the 404.html file generated by gatsby, which includes the client-side router. Once the router completes its initialization it reads the path and loads the correct page. Meaning you end up at the right place but there's half a second of landing on the wrong page.

How to fix It

The general solution is to tell your server to redirect the /sample/ path to your /sample/index.htmlfile. The way to do this depends on your host, but I'll provide the name of the technique for various hosts in case you want to look it up. It's usually called URL Rewriting and should be supported by every major hosting platform.

Heroku

The Heroku section of the gatsby deploy documentation suggests using the heroku-buildpack-static module which has built-in support for "custom routes" which will solve this for your case using syntax like this:

{
  "routes": {
    "/sample/**": "sample/index.html",
  }
}

AWS Amplify

You need to add the redirect in the AWS Amplify console. For this example, the params are:

  • Source URI: /sample/<*>
  • Target URI: /sample/index.html
  • Type: 200
Dagobert Renouf
  • 2,078
  • 2
  • 14
  • 14
Jamund Ferguson
  • 16,721
  • 3
  • 42
  • 50
  • 4
    this works perfectly (key is specifying index.html). This should be the accepted answer. – Dagobert Renouf Aug 01 '19 at 11:09
  • I tried with firebase hosting as you've suggested. But not, working. Can you elaborate and example for firebase, please? – Md Abdul Halim Rafi May 25 '20 at 08:32
  • You can't have 200 with CloudFlare. Only 301/302 forwarding is allowed. (The new CloudFlare Pages might have a solution for this, but I still haven't received the beta access, so I'm only guessing) – mauleros Jan 02 '21 at 10:51
  • Thanks, this got me on the right path to fixing this problem on Vercel with rewrites. Solution here: https://stackoverflow.com/a/66120629/77158 – Paul McClean Feb 09 '21 at 14:27
  • 1
    Works great, I had this issue again and found this answer and then realised I had liked it before, thanks again for sharing! – codeguy Jun 24 '22 at 10:52
  • having this issue locally when running gatsby. is this solution supposed to work on local node server(npm run dev/start) ? – awm Aug 24 '22 at 13:19
  • Hi, i'm using apache as webserver. My problem is that I've parameters in the url. How can I modify the configuration of Apache2 to redirect the parameters too? Otherwise the canonical url is always the same and it does not show the right parameters. Thanks a lot – Angelo Mantellini Dec 11 '22 at 19:48
4

This solves my problem

pages/404.jsx

import React, { useEffect, useState } from 'react';

export default ()=>{
  const [isMount, setMount] = useState(false);
  
  useEffect(() => {
        setMount(true);
  },[])

  if(!isMount) {
     return(
        <div>loading</div>
     )
  }

  return (
     <div>Page Not Found</div>
  )


}

This will not show a 404 error on the client-side render until the response is 404.

Murtaza Huzaifa
  • 149
  • 1
  • 4
2

For anyone who uses S3 (gatsby-plugin-s3) and CloudFront, I've resolve 302/404 by adding generateMatchPathRewrites: false in the gatsby-plugin-s3 config and creating a Lambd@Edge function for origin request with code below:

exports.handler = async (event) => {
  const request = event.Records[0].cf.request;
  if (/^\/app\//i.test(request.uri)) {
      request.uri = '/app/index.html';
  }
  return request;
};

Though 302/404 went away, I still have the issue when doing a hard refresh. For example when I am at /app/page2 and hit refresh, the default component in /app will load then half a second later the component in /app/page2 will show up but in a weird way. Some css classes of /app is mixed in /app/page2. If anyone has any idea about this, please let me know.

ian
  • 1,505
  • 2
  • 17
  • 24
  • This is working for me. I am not seeing any styling issues at the moment. I now have my S3 bucket completely private! Only accessible from CloudFront and refreshes work on client only routes! – natac May 26 '20 at 14:51
2

Vercel

For anyone looking trying to solve this on Vercel platform, the answer is rewrites: https://vercel.com/docs/configuration#project/rewrites

If you have a gatsby app with client-only paths, as per something like https://www.gatsbyjs.com/plugins/gatsby-plugin-create-client-paths/, it's likely you will encounter this issue on deployment.

Example: your application has a path like mything.com/app/user-profile.

In this case, app/* are client-only routes that don't get generated on the server, so you may get a flash of a 404 when you reload this route or land on it directly from an external link, for example.

To solve, add a rewrite on the /app/* server side route in Vercel to point back to /app where, it is assumed, your routing is controlled by Reach router or similar in your pages/app.js file.

To set this up on Vercel, create a vercel.json in the root of your gatsby/react project and add the following configuration:

{
  "rewrites": [{ "source": "/app/:match*", "destination": "/app" }]
}

This issue will generally only manifest itself when deployed to production/staging. It won't occur in development (via gatsby develop anyway).

Paul McClean
  • 101
  • 1
  • 7
1

I looked in the public folder to find the index file for the client route I created

for netlify I added the below to the netlify.toml file at the root.


[[redirects]]
  from = "/user/dashboard/"
  to = "/user/index.html"
  status = 200

Stefan T
  • 111
  • 8
1

For Firebase hosting, I added the following to my firebase.json:

"rewrites": [
  {
      "source": "**",
      "destination": "/loading/index.html"
  }

]

Then I created a new loading.js file under /page. With this configuration, Firebase first responds with loading.html then Gatsby handles the client routing.

TheSchnaz
  • 21
  • 3
1

For local development, you have to use the magic [...].js filename.

You can take a look at the demo repo here: https://github.com/gatsbyjs/gatsby/tree/master/examples/client-only-paths/src/pages

Cesar Varela
  • 5,004
  • 2
  • 16
  • 17
1

NGINX

If your environment includes Nginx you should add something like this into your virtual host config:

rewrite ^/products/([0-9]+)$ /products/[id]/index.html;

or

rewrite ^/users/confirm/(.+)$ /users/confirm/[code]/index.html;

etc.

Just go to your production root (by default this is a /public directory) and look for the correct path to the index.html file for your case. For example: /public/products/[id]/index.html

Usually your virtual host config is located here: /etc/nginx/sites-enabled/mysite.com.conf

Dzmitry Kulahin
  • 1,726
  • 2
  • 17
  • 21
0

The heroku solution was not working for me, but I don't know exactly why. My frontend is fetching data from a express.js back end. I solved my problem by adding the following lines in my server.js to redirect my clients only routes.

app.get('/user/**', (req, res) => {
  res.sendFile(path.join(`${__dirname}/public/user/index.html`));
});
quilmes
  • 43
  • 5
0

For anyone using Gatsby Cloud, you add this to your gatsby-node.js

Working with Redirects and Rewrites

// gatsby-node.js

exports.createPages = async ({ actions: { createRedirect } }) => {
 
  ...

  createRedirect({
    fromPath: '/sample/*',
    toPath: '/sample/index.html
  });
};

Paul
  • 151
  • 10