Gatsby: [v2] Refreshing a page then hitting back (browser back button) leads to 404

Created on 12 Aug 2018  路  18Comments  路  Source: gatsbyjs/gatsby

Description

I have a local project that is using the v2 of Gatsby. Eevrything works great except for the fact that a page refresh and a back button on the browser always leads to a 404. On that 404 page Gatsby displays a list of routes and the actual route (the one that responded with 404) is in that list.

http://localhost:8000/categories/release/
-----
Gatsby.js development 404 page
There's not a page yet at /categories/release/

Create a React.js component in your site directory at src/pages/categories/release.js and this page will automatically refresh to show the new page component you created.

If you were trying to reach another page, perhaps you can find it below.

Pages (7)
/tags/cards/
/tags/example/
/tags/release/
/categories/release/
/404/
/
/404.html

Environment

gatsby info --clipboard

  System:
    OS: Linux 4.17 Fedora 28 (Workstation Edition) 28 (Workstation Edition)
    CPU: x64 Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
    Shell: 4.4.23 - /bin/bash
  Binaries:
    Node: 10.7.0 - /usr/bin/node
    Yarn: 1.9.4 - /usr/bin/yarn
    npm: 6.1.0 - /usr/bin/npm
  Browsers:
    Chrome: 68.0.3440.75
    Firefox: 61.0.1
  npmPackages:
    gatsby: next => 2.0.0-beta.97 
    gatsby-1-config-css-modules: next => 1.0.10-13 
    gatsby-image: next => 2.0.0-beta.7 
    gatsby-plugin-google-fonts: 0.0.4 => 0.0.4 
    gatsby-plugin-mailchimp: 2.2.3 => 2.2.3 
    gatsby-plugin-netlify: ^2.0.0-beta.6 => 2.0.0-beta.6 
    gatsby-plugin-react-helmet: next => 3.0.0-beta.4 
    gatsby-plugin-sass: next => 2.0.0-beta.10 
    gatsby-plugin-sharp: next => 2.0.0-beta.7 
    gatsby-plugin-styled-components: next => 3.0.0-beta.3 
    gatsby-plugin-typescript: next => 2.0.0-beta.9 
    gatsby-source-contentful: next => 2.0.1-beta.15 
    gatsby-source-filesystem: next => 2.0.1-beta.10 
    gatsby-transformer-sharp: next => 2.1.1-beta.6 
  npmGlobalPackages:
    gatsby-cli: 1.1.58
    gatsby: 2.0.0-beta.54

File contents (if changed)

gatsby-config.js:

let contentfulConfig, mailchimpConfig;

try {
    contentfulConfig = require('./config/contentful.json');
} catch (_) {
    contentfulConfig = {
        spaceId: process.env.CONTENTFUL_SPACE_ID,
        accessToken: process.env.CONTENTFUL_DELIVERY_TOKEN,
    }
}

try {
    mailchimpConfig = require('./config/mailchimp.json')
} catch (_) {
    mailchimpConfig = {
        endpoint: process.env.MAILCHIMP_ENDPOINT
    }
}

module.exports = {
    plugins: [
        'gatsby-plugin-react-helmet',
        `gatsby-plugin-typescript`,
        `gatsby-transformer-sharp`,
        `gatsby-plugin-sharp`,
        `gatsby-plugin-sass`,
        `gatsby-plugin-styled-components`,
        {
            resolve: `gatsby-plugin-google-fonts`,
            options: {
                fonts: [
                    `Roboto:300,300i,400,400i,500,500i,600,600i`,
                ],
            },
        },
        {
            resolve: `gatsby-source-contentful`,
            options: contentfulConfig,
        },
        {
            resolve: 'gatsby-plugin-mailchimp',
            options: mailchimpConfig,
        },
        `gatsby-plugin-netlify`, // make sure to keep it last in the array
    ],
};

package.json:

{
  "name": "",
  "description": "",
  "version": "1.0.0",
  "author": "",
  "dependencies": {
    "@blueprintjs/core": "^3.0.1",
    "autoprefixer": "^9.0.1",
    "babel-plugin-styled-components": "^1.5.1",
    "extract-text-webpack-plugin": "^3.0.2",
    "gatsby": "next",
    "gatsby-1-config-css-modules": "next",
    "gatsby-image": "next",
    "gatsby-plugin-google-fonts": "0.0.4",
    "gatsby-plugin-mailchimp": "2.2.3",
    "gatsby-plugin-netlify": "^2.0.0-beta.6",
    "gatsby-plugin-react-helmet": "next",
    "gatsby-plugin-sass": "next",
    "gatsby-plugin-sharp": "next",
    "gatsby-plugin-styled-components": "next",
    "gatsby-plugin-typescript": "next",
    "gatsby-source-contentful": "next",
    "gatsby-source-filesystem": "next",
    "gatsby-transformer-sharp": "next",
    "grid-styled": "^5.0.2",
    "install": "^0.12.1",
    "lodash": "^4.17.10",
    "lodash.debounce": "^4.0.8",
    "moment": "^2.22.2",
    "node-sass": "^4.9.2",
    "npm": "^6.3.0",
    "prismjs": "^1.15.0",
    "react": "^16.4.1",
    "react-accessible-accordion": "^2.4.4",
    "react-custom-scrollbars": "^4.2.1",
    "react-dom": "^16.4.1",
    "react-helmet": "^5.2.0",
    "react-player": "^1.6.4",
    "react-prism": "^4.3.2",
    "react-redux": "^5.0.7",
    "react-share": "^2.2.0",
    "react-slick": "^0.23.1",
    "remark": "^9.0.0",
    "remark-react": "^4.0.3",
    "slick-carousel": "^1.8.1",
    "styled-components": "^3.3.3",
    "tinycolor2": "^1.4.1"
  },
  "keywords": [
    "static site",
    "cards",
    "smart cards",
    "blog"
  ],
  "license": "MIT",
  "scripts": {
    "build": "gatsby build",
    "develop": "gatsby develop",
    "format": "prettier --write '**/*.js'",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "devDependencies": {
    "@types/grid-styled": "^4.2.0",
    "@types/lodash": "^4.14.115",
    "@types/moment": "^2.13.0",
    "@types/node": "^10.5.5",
    "@types/prismjs": "^1.9.0",
    "@types/prop-types": "^15.5.4",
    "@types/reach__router": "^1.0.0",
    "@types/react": "^16.4.7",
    "@types/react-dom": "^16.0.6",
    "@types/react-helmet": "^5.0.6",
    "@types/react-redux": "^6.0.5",
    "@types/react-share": "^2.1.1",
    "@types/react-transition-group": "^2.0.13",
    "@types/tinycolor2": "^1.4.1",
    "prettier": "^1.13.7",
    "redux-devtools-extension": "^2.13.5"
  },
  "repository": {
    "type": "git",
    "url": ""
  }
}

gatsby-node.js:

const _ = require('lodash');
const Promise = require('bluebird');
const path = require('path');
const {createFilePath} = require(`gatsby-source-filesystem`);
const {createRemoteFileNode} = require(`gatsby-source-filesystem`);

const getTags = (edges) => {
    const tags = [];

    _.each(edges, edge => {
        if (_.get(edge, "node.tags")) {
            for (let i = 0; i < edge.node.tags.length; i++) {
                tags.push(edge.node.tags[i]);
            }
        }
    });

    return tags;
};

const getCategories = (edges) => {
    const categories = [];

    _.each(edges, edge => {
        if (_.get(edge, "node.categories")) {
            for (let i = 0; i < edge.node.categories.length; i++) {
                categories.push(edge.node.categories[i]);
            }
        }
    });

    return categories;
};

exports.createPages = ({graphql, actions}) => {
    const {createPage} = actions;

    return new Promise((resolve, reject) => {
        const postTemplate = path.resolve('./src/templates/post/index.tsx');
        const tagTemplate = path.resolve("./src/templates/tag/index.tsx");
        const categoryTemplate = path.resolve("./src/templates/category/index.tsx");

        resolve(graphql(`
                {
                    allContentfulPost(sort: {fields: [createdAt], order: DESC}) {
                        edges {
                            node {
                                id
                                slug

                                tags {
                                    id
                                    slug
                                    title
                                }

                                categories {
                                    id
                                    slug
                                    title
                                }

                                createdAt
                                updatedAt
                            }
                        }
                    }
                }
            `).then(result => {
            if (result.errors) {
                console.error(result.errors);
                reject(result.errors);
            }

            const edges = result.data.allContentfulPost.edges;

            const tags = getTags(edges);
            const categories = getCategories(edges);

            // Create posts and pages.
            _.each(edges, (edge, index) => {
                const slug = edge.node.slug;

                const previous = index === edges.length - 1 ? null : edges[index + 1].node;
                const next = index === 0 ? null : edges[index - 1].node;

                //  create posts pages
                createPage({
                    path: `/posts/${slug}/`,
                    component: postTemplate,
                    context: {
                        slug: slug,
                        previous,
                        next,
                    },
                });
            });

            // Make tag pages
            _.uniq(tags).forEach(tag => {
                createPage({
                    path: `/tags/${tag.slug}/`,
                    component: tagTemplate,
                    context: {
                        slug: tag.slug
                    },
                })
            });

            // Make category pages
            _.uniqBy(categories, 'id').forEach(category => {
                createPage({
                    path: `/categories/${category.slug}/`,
                    component: categoryTemplate,
                    context: {
                        slug: category.slug
                    },
                })
            });
        }));
    });
};

gatsby-browser.js: N/A
gatsby-ssr.js: N/A

NO console errors shown.

help wanted bug

Most helpful comment

Ok, got this working in a rather brutal way. For those who want a quick (no forking required), but very dirty fix:


Click to see dirty fix 馃檲

// gatsby-browser.js

exports.onInitialClientRender = () => {
  // dirty fix for missing popstate listener
  const GATSBY_NAVIGATE = window.___navigate || {}

  window.addEventListener('popstate', () =>
    GATSBY_NAVIGATE(window.location.pathname, { replace: true })
  )
}

Explanation: there is no listener for the popstate event thus the Gatsby site has no clue the route has changed. We can patch this using the exposed __navigate method with replace: true param. It will manage all the prefetching & rendering work. Thanks to { replace: true } flag, the navigation event coming from the reach router will be "meged & squashed" with the popstate one.

@Chuloo If you could please help me out on where to put this listener within the codebase, I'd be more than happy to come up with a PR. I guess somewhere here might be a good place?

Cheers

All 18 comments

Update:

I tried logging the find-page.js file in the .cache directory and printed if it finds the requested page. The result is odd because it first matches the requir3ed component and then it matches against the 404 again:

Route WAS found for /categories/release/. Tried against /categories/release/
...
Route WAS found for /404.html. Tried against /404.html

These matches occur in different blocks however:

pages.some(page => {
        let pathToMatch = page.matchPath ? page.matchPath : page.path



        if (matchPath(pathToMatch, trimmedPathname)) {
            foundPage = page
            pageCache[trimmedPathname] = page

            console.log(`Route WAS found for ${pathToMatch}. Tried against ${trimmedPathname}`);
            return true
        }

        // Finally, try and match request with default document.
        if (matchPath(`${page.path}index.html`, trimmedPathname)) {
            foundPage = page
            pageCache[trimmedPathname] = page

            console.log(`Route WAS found for ${page.path}index.html. Tried against ${trimmedPathname}`);
            return true
        }

        console.log(`Route was not found for ${pathToMatch}. Tried against ${trimmedPathname}`);

        return false
    })

Maybe this helps investigate a bit further.

@ciokan can you send in a minimal reproduction using a demo account? this should help fix the issue 馃憤

Hello! 馃憢

I've run into a similar issue. Managed to reproduce it with a plain starter.

Steps to reproduce:

  1. Clone the default starter for v2.
  2. Deploy to Netlify. The build is also available here (w/o css): https://hungry-payne-c80795.netlify.com/.
  3. Go to home.
  4. Navigate to the second page.
  5. Hit reload.
  6. Navigate back. Notice the page doesn't get updated.
  7. Hover over the link to the home page. Notice the page updates once the link is hovered.

gatsby-v2-routing

I guess we should look somewhere along these lines. Not enough time to get into this now 馃槬 . Will let you know if something pops out!

Hello again 馃憢!

I've narrowed it down a bit: the component is not rendered up until it is prefetched.

Since popstate events does not trigger the prefetching, only hovering the link (or the initial load) causes the page content to be prefetched and then displayed.

The pathScriptsCache returns null here. Also I stumbled upon this comment.

Not sure if any of this is any helpful 馃槄.

Ok, got this working in a rather brutal way. For those who want a quick (no forking required), but very dirty fix:


Click to see dirty fix 馃檲

// gatsby-browser.js

exports.onInitialClientRender = () => {
  // dirty fix for missing popstate listener
  const GATSBY_NAVIGATE = window.___navigate || {}

  window.addEventListener('popstate', () =>
    GATSBY_NAVIGATE(window.location.pathname, { replace: true })
  )
}

Explanation: there is no listener for the popstate event thus the Gatsby site has no clue the route has changed. We can patch this using the exposed __navigate method with replace: true param. It will manage all the prefetching & rendering work. Thanks to { replace: true } flag, the navigation event coming from the reach router will be "meged & squashed" with the popstate one.

@Chuloo If you could please help me out on where to put this listener within the codebase, I'd be more than happy to come up with a PR. I guess somewhere here might be a good place?

Cheers

Sorry guys I've been offline for a while. I'll give it a try and report back if the suggested fix by @fjaskolski is working.

Yes. it Works!

_Putting this here for ref_
Bug is visible using the Gatsby default starter v2. On gatsby develop, nothing displays when the back button is clicked. On gatsby build the current page is retained even with the new URL.

Repro: https://github.com/Chuloo/gatsby-7261

Thanks for the reproduction @Chuloo!

I tried reproducing this in the latest rc and couldn't. I'm pretty sure this is one of the bugs @davidbailey00 fixed in https://github.com/gatsbyjs/gatsby/pull/7355 so closing this now.

Just upgraded to [email protected] and the bug is still there.

@KyleAMathews

I've cloned gatsby-starter-default on the latest RC and whilst it works when going between pages, there's an issue when using the back button between _templates_ manually created in gatsby-node.js

This can be reproduced here (Chrome 68):
https://gatsby-v2-issue-7261.netlify.com/

  • Click 'Go to template 1'
  • Click 'Template 2'
  • Reload the page in your browser
  • Press the back button

At this point, the URL changes but the title remains the same.

Curiously, on development, you just get a blank screen. See below:

kapture 2018-08-31 at 0 15 43

Other observations:

  • It works fine when going from page > template and vice versa
  • Using the fix mentioned by @fjaskolski also continues to work
  • It works on Firefox 61 and Safari 11.1.2 on production, but fails in development (!)
  • In Safari (production), clicking the first 'Go to template 1' link does a hard load of the next page and appends ?no-cache=1 to the URL

Can anyone corroborate the above issues? I'm starting to think i'm going a bit crazy.

gatsby-node.js

const path = require('path')

exports.createPages = ({ actions }) => {
  const { createPage } = actions
  createPage({
    component: path.resolve('./src/templates/template-1.js'),
    path: '/template-1',
  })
  createPage({
    component: path.resolve('./src/templates/template-2.js'),
    path: '/template-2',
  })
}

templates/template-1.js

import React from 'react'
import { Link } from 'gatsby'

import Layout from '../components/layout'

const WithLayout = (props) => {
  return (
    <Layout>
      <h1>Template 1</h1>
      <Link to="/template-2">Template 2</Link>
    </Layout>
  )
}

templates/template-2.js

import React from 'react'
import { Link } from 'gatsby'

import Layout from '../components/layout'

const WithLayout = (props) => {
  return (
    <Layout>
      <h1>Template 2</h1>
      <Link to="/">Back home</Link>
    </Layout>
  )
}
export default WithLayout

gatsby-clipboard:

  System:
    OS: macOS High Sierra 10.13.6
    CPU: x64 Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz
    Shell: 5.3 - /bin/zsh
  Binaries:
    Node: 8.11.1 - ~/.nvm/versions/node/v8.11.1/bin/node
    Yarn: 1.9.4 - ~/.nvm/versions/node/v8.11.1/bin/yarn
    npm: 5.6.0 - ~/.nvm/versions/node/v8.11.1/bin/npm
  Browsers:
    Chrome: 68.0.3440.106
    Firefox: 61.0.1
    Safari: 11.1.2
  npmPackages:
    gatsby: next => 2.0.0-rc.4
    gatsby-plugin-manifest: next => 2.0.2-rc.1
    gatsby-plugin-offline: next => 2.0.0-rc.1
    gatsby-plugin-react-helmet: next => 3.0.0-rc.1
  npmGlobalPackages:
    gatsby-cli: 1.1.58

Oh huh odd. I see it now. I think it's something to do with gatsby-plugin-offline. When I was trying to reproduce it, it wasn't using gatsby-plugin-offline.

Could you post a link to the source code?

@davidbailey00 any idea off-hand what's going on?

Sure: https://github.com/robinpyon/gatsby-v2-issue-7261

It's just gatsby-starter-default with a few basic templates added.

FWIW, the issue continues to persist for me in another project that doesn't use gatsby-plugin-offline. It too, makes extensive use of templates and not pages.

Not sure what's wrong just on the off-hand, but I can check this out later. It could be something I broke in #7355 to be honest...

Just reopening this because I haven't verified if it's fixed yet - GitHub thought it was fixed because I wrote "fixes" before the number

image

Edit: yep, I'm still able to reproduce this after #7788.

In loader, this piece of code is not able to find page if page's matchPath is altered. Instead of page.path, page.matchPath should be used. Otherwise, findPage is returning null.

// loader.js
if (process.env.NODE_ENV !== `production`) {
        const page = findPage(path)
        const pageResources = {
          component: syncRequires.components[page.componentChunkName],
          page,
        }
...
}

@alpgumus Thanks for reporting - please can you open a new issue with a reproduction repo? This will help us fix the problem more quickly!

Also, can you try updating your packages? I think the issue you've described should have been fixed in #8083, but I'm not sure if it's the same problem exactly - a reproduction will help us figure that out

hmm, I don't think const page = findPage(path) is even needed there, because page is already defined outside of if (process.env.NODE_ENV !==production) { block

Was this page helpful?
0 / 5 - 0 ratings

Related issues

magicly picture magicly  路  3Comments

andykais picture andykais  路  3Comments

mikestopcontinues picture mikestopcontinues  路  3Comments

brandonmp picture brandonmp  路  3Comments

KyleAMathews picture KyleAMathews  路  3Comments