Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable Client-Side Routing? #4337

Closed
TuckerWhitehouse opened this issue Mar 2, 2018 · 77 comments · Fixed by wardpeet/gatsby-plugin-static-site#4
Closed

Disable Client-Side Routing? #4337

TuckerWhitehouse opened this issue Mar 2, 2018 · 77 comments · Fixed by wardpeet/gatsby-plugin-static-site#4
Labels
status: awaiting author response Additional information has been requested from the author type: question or discussion Issue discussing or asking a question about Gatsby

Comments

@TuckerWhitehouse
Copy link
Contributor

Description

I have a use case where the server is defining some custom routes. When the browser loads these routes, the expected content is shown for a brief moment until client side routing takes over and replaces the page with the 404 because the url in the browser is not recognized.

My first thought was that maybe the matchPath could be used here, but I won't necessarily know the url patterns that would render these pages, and there may be some overlap in what the url is, and what page is returned.

I'm guessing it may be possible with some hook into find-page but I'm not sure what that would look like.

Environment

Gatsby version: 1.9.221
Node.js version: 8.9.1
Operating System: macOS

Actual result

After the browser loads, the expected page is shown briefly until the javascript loads, determines the url is unknown, and renders the 404 page.

Expected behavior

The server rendered page should be available at the custom url, and not be replaced by the 404 page when the client loads.

Steps to reproduce

1. git clone https://github.com/TuckerWhitehouse/gatsby-client-routing-issue

2. npm install

3. npm run build

4. npm start

5. open http://localhost:3000

@KyleAMathews
Copy link
Contributor

Why don't you know what paths the server is rendering?

@TuckerWhitehouse
Copy link
Contributor Author

It's less that I won't know the paths (I can write a regex that will match them) but more the overlap. The actual setup is behind an apache server that reverse proxies to a bunch of different apps, including the gatsby site. If any of those apps become unavailable, or return an internal server error, we return a custom error page that is part of the gatsby site.

So at any point in time, if app1 is unavailable or misbehaving, any requests to /app1 would return the content of /error/unavailable.html or /error/internal.html, and the same would be true for app2, and so on.

Using a matchPath like /^(app1|app2)/.*/, on both the unavailable an internal error pages doesn't work because findPage doesn't know (based on the url) which page I actually intend to show the user.

@TuckerWhitehouse
Copy link
Contributor Author

I was able to get something working using a global variable and "patching" ___history and ___loader in onClientEntry. It's very fragile because of the dependency on gatsby exposing those globals - not sure if there is some way to generalize this and add it to gatsby.

// gatsby-browser.js
exports.onClientEntry = () => {
  // Check for a custom pathname
  const pathname = global.___pathname
  if (!pathname) return

  // Override the history location
  const history = global.___history
  history.location.pathname = pathname

  // Patch the resource loader
  const loader = global.___loader
  const { getResourcesForPathname } = loader

  loader.getResourcesForPathname = (path, ...args) => {
    return getResourcesForPathname(path === location.pathname ? pathname : path, ...args)
  }
}
// src/pages/page1.js
import React from "react"
import Helmet from 'react-helmet'

export default () => (
  <div>
    <Helmet>
      <script>{`window.___pathname = '/page1'`}</script>
    </Helmet>
    <div>Page 1!</div>
  </div>
)

@fk fk added the type: question or discussion Issue discussing or asking a question about Gatsby label Mar 7, 2018
@master12
Copy link

I also agree that there should be a build option to disable this feature. We also have an unconventional setup and would like to disable this feature temporarily while we finish our migration to a full Gatsby site.

A simple flag at build time would be perfect.

@ichenzhifan
Copy link

how about this one? any solution for this?

@master12
Copy link

We ended up modifying the pages.json to match the path that we needed. We basicallay called addPagesArray with the corrected path name.

I still dont understand why this throws an error? The page loads fine and works. This at most should be a warning when it cant match the path.

That being said I dont know if there is a more elegant way of modifying the pages.json thru a config vs runtime code.

@alex-greco-harrys
Copy link

alex-greco-harrys commented Dec 6, 2018

I want to bump this issue.

A project I am working on is experiencing a similar issue. We are build a landing page generator that will build single-page Gatsby apps. This issue comes when we try to serve a landing page outside of it's domain.

So for example we have our main Gatsby app www.example.com. We have a service that will take the Gatsby landing pages and serve them at www.example.com/trial. So a landing page URL would look like www.example.com/trail/ad-123 The page initially loads fine until all the JS loads and the router takes over. The landing page looks at the path and doesn't know where it is, so it tries to change the path to place the page at the root, looking like this www.example.com/ad-123, which results in a 404 redirect.

Are there any plans to add a configurable option to fix this? Would the Gatsby team be open to a PR?

@jgierer12
Copy link
Contributor

jgierer12 commented Dec 6, 2018

@alex-greco-harrys It seems to me that a path prefix is what you'll want to use in that scenario.

@callicoder
Copy link

I also needed to disable client side routing to run Google Adsense properly on my website.

Google Adsense auto ads don't detect client side routing and the ads don't refresh when routes are updated.

Is there anyway I can disable client side routing?

@pieh
Copy link
Contributor

pieh commented Dec 17, 2018

You can use a tags instead of gatsby-link in cases like that

@alex-greco-harrys
Copy link

I was able to get something working using a global variable and "patching" ___history and ___loader in onClientEntry. It's very fragile because of the dependency on gatsby exposing those globals - not sure if there is some way to generalize this and add it to gatsby.

// gatsby-browser.js
exports.onClientEntry = () => {
  // Check for a custom pathname
  const pathname = global.___pathname
  if (!pathname) return

  // Override the history location
  const history = global.___history
  history.location.pathname = pathname

  // Patch the resource loader
  const loader = global.___loader
  const { getResourcesForPathname } = loader

  loader.getResourcesForPathname = (path, ...args) => {
    return getResourcesForPathname(path === location.pathname ? pathname : path, ...args)
  }
}
// src/pages/page1.js
import React from "react"
import Helmet from 'react-helmet'

export default () => (
  <div>
    <Helmet>
      <script>{`window.___pathname = '/page1'`}</script>
    </Helmet>
    <div>Page 1!</div>
  </div>
)

@TuckerWhitehouse where are you getting ___history, ___loader from? When I try to replicate your example those two properties of global are undfined.

@alex-greco-harrys
Copy link

@alex-greco-harrys It seems to me that a path prefix is what you'll want to use in that scenario.

@jgierer12 That helps solve the first piece of my issue. The second piece is that the final path is unknown until the page is rendered. We have a learning service that takes a collection of static pages and serves them based off conversion rates. So at a path example.com/go/ we could be serving 1 of a collection of pages. So we wouldn't be serving the page at a path like example.com/go/first-page or example.com/go/second-page. Those both would be served at example.com/go/page path.

Essentially what I am trying to accomplish is to serve a gatsby page at whatever path I want.

@TuckerWhitehouse
Copy link
Contributor Author

@alex-greco-harrys those globals were exposed by gatsby v1. With the upgrade to v2, I know the underlying router was switched from react-router to reach-router, so my guess would be those globals were affected.

@ethagnawl
Copy link

ethagnawl commented Jan 10, 2019

I'm also hoping to use Gatsby to build a single page application and would like to disable routing entirely. Does anyone know of a workaround (a la @TuckerWhitehouse's) that would be compatible with V2?

UPDATE:
While I wasn't able to find a solution which would disable client side routing, I was able to prevent the redirect referenced by @alex-greco-harrys and others by setting:

window.page = window.page || {};
window.page.path = window.location.pathname;

in gatsby-browser.js which short circuits this conditional check in production-app.js. That conditional redirect attempts to "make the canonical path match the actual path" and results in the (IMO) unexpected behavior referenced above.

@Neddz
Copy link

Neddz commented Jan 14, 2019

I also need this.

I am currently using code generated by Gatsby on another project and I use it on multiple pages. I am using Gatsby as it generates static code. Therefore, I used the pathPrefix so I could generate everything under a specific path and serve it. That way, everything gets requested there and then rendered as a fragment of a page. However, I get unwanted redirects all the time to the pathPrefix because it is in the scripts. I have to manually remove the condition that @ethagnawl mentioned everytime I build. I just tried his solution but it didn't work for me.

@alex-greco-harrys
Copy link

I'm also hoping to use Gatsby to build a single page application and would like to disable routing entirely. Does anyone know of a workaround (a la @TuckerWhitehouse's) that would be compatible with V2?

UPDATE:
While I wasn't able to find a solution which would disable client side routing, I was able to prevent the redirect referenced by @alex-greco-harrys and others by setting:

window.page = window.page || {};
window.page.path = window.location.pathname;

in gatsby-browser.js which short circuits this conditional check in production-app.js. That conditional redirect attempts to "make the canonical path match the actual path" and results in the (IMO) unexpected behavior referenced above.

@ethagnawl I have a hacky sort of solution to produce a single page app that can be served at any URL. By single page, I actually mean one single page with no routing at all.

If you look at the following Gatsby example: https://github.com/gatsbyjs/gatsby/tree/master/examples/client-only-paths .

You can edit this file on line 15 to look like <Page path="/*" {...props} /> and delete line 16. When you build this application, any path will result in serving the Page you have defined. From there you can make that Page whatever you want. Now if you need to host this page at an arbitrary path you will see no redirection.

I was unable to figure out how this solution could work with multiple pages in an app. The goal for my project was to serve a single Gatsby page (marketing landing page) at any URL I want.

Not sure if this helps in your use case, but maybe this can fuel some future discovery!

@jtwillig
Copy link

jtwillig commented Feb 1, 2019

I was able to accomplish this by following the Customizing html.js directions in the docs and removing {this.props.postBodyComponents}

https://www.gatsbyjs.org/docs/custom-html/

@ethagnawl
Copy link

Based on how active this thread continues to be, there seems to be a non-trivial number of users who desire this behavior.

To quickly reiterate my use case: I want to (did!) use Gatsby as a static page generator -- as opposed to a static site generator -- and because the Gatsby "page" is injected into a containing page whose URL is out of my control and subject to change, I don't want the Gatsby application ever mucking with the URL. Out of the box, Gatsby mostly supports this use case and is a great pleasure to use, but it does makes some assumptions -- again, because of its standard static site use case -- that result in the need for hacks like the ones mentioned above.

So, is there any hope for the ability to disable client side routing becoming a top-level config option? I would be happy to submit a PR, but don't want to sink time into it if there's no chance it'll be accepted.

@KyleAMathews
Copy link
Contributor

This seems like a reasonable feature to add @ethagnawl. I think it'd need a very long and obnoxious name like dangeouslySetInnerHTML so people are fully conscious of what they're doing as this is a very special edge case.

@ethagnawl
Copy link

My first pass at a PR addressing this issue can be found here. I'd greatly appreciate feedback from maintainers and/or other users who've bumped into this issue.

@wardpeet
Copy link
Contributor

wardpeet commented Feb 20, 2019

Thanks for creating aPR @ethagnawl

could you remind me again why won't the following work?

// Implement the Gatsby API “onCreatePage”. This is
// called after every page is created.
exports.onCreatePage = ({ page, actions }) => {
  const { createPage } = actions;
  page.matchPath = `${page.path}*`;
  createPage(page);
};

@ethagnawl
Copy link

ethagnawl commented Feb 20, 2019

@wardpeet I'm sure that will work and looks similar to the solution I mentioned above. However, those sorts of solutions are hard to document and potentially fragile (see the solution offered by @TuckerWhitehouse which no longer works).

IMO, codifying this concept is worthwhile as, again, it makes documentation more straightforward and this flag could also be used to make additional optimizations by bypassing/noop-ing/etc. functionality that isn't relevant when Gatsby is being used in this way.

@TuckerWhitehouse
Copy link
Contributor Author

Additionally, using the matchPath requires the url in the browser to reflect the page you want to render, but this breaks down when you are injecting a gatsby site into an unknown location. (My original issue was around having gatsby behind an apache reverse proxy and not knowing the routes that would cause a certain page to render).

@ethagnawl do you think it would be possible to disable routing at the page level (something like page.__disable_client_side_routing__ = true)? This would probably resolve the original issue I was having as well.

@ethagnawl
Copy link

do you think it would be possible to disable routing at the page level

I don't see why not? Would that be in addition to or in place of my proposed solution? If it's the latter, is there any advantage to doing that at the page level?

@wardpeet
Copy link
Contributor

wardpeet commented Mar 1, 2019

I've setup this repo :)
https://github.com/wardpeet/gatsby-plugin-static-site

unsure if this works for your use cases.
For now you need to do

git clone https://github.com/wardpeet/gatsby-plugin-static-site
npm install
npm run build
npm link

cd "into your project"
npm link gatsby-plugin-static-site

add gatsby-plugin-static-site to your gatsby-config.js

Let me know if this is ok for your use case, I have no intention to actually support it so i'm happy to transfer it 😄

@wardpeet
Copy link
Contributor

wardpeet commented Mar 6, 2019

I updated the repo as I had something wrong in my gitignore file (thanks @m-allanson). I also published it to npm under my own name.

so installation can be done

npm install --save @wardpeet/gatsby-plugin-static-site

and add @wardpeet/gatsby-plugin-static-site to gatsby-config.json

If this is looking good then I can add some test and some options to disable this behaviour for develop.

@wardpeet wardpeet added the status: awaiting author response Additional information has been requested from the author label Mar 6, 2019
@wardpeet
Copy link
Contributor

wardpeet commented Jun 27, 2019

We rather don't want to enable these escape hatches in core. What I basically understand of the issue, is this:

I have a gatsby site and a path /my-special-path and on my server I have a route called /something-else. If I rewrite /something else to /gatsby/my-special-path, it won't work because it tries to change the page into /my-special-path?

If so I'll see if I can fix it in my plugin. Do you maybe have a live demo of this?

@xavivars
Copy link
Contributor

Yes, that is exactly the problem. I'll try to put together another PR (that doesn't add something that invasive as the global config variable as #15173).

I have something that may be acceptable that I will push as another PR in a few minutes

@xavivars
Copy link
Contributor

@wardpeet this is what I think woudl be needed to add to Gatsby so your plugin could be extended. I've added some examples and documentation on the PR

#15180

@xavivars
Copy link
Contributor

xavivars commented Jul 3, 2019

After a conversation with @DSchau in Discord, it seems the core contributors are aligned that a solution like #15173 or #15180 shouldn't live in core, but in a plugin. So I'd like to explore other options to solve it.

Currently the only ways I found was via a global config variable (#15173 ) to shortcircuit the canonical redirect check, or by allowing to modify the perceived rendered URL for gatsby (#15180), so the canonical redirect check doesn't directly depend on window.location, but on a filterable variable.

IMHO, the challenge is to use a plugin to extend/override something that doesn't seem to be extensible/overridable right now (directly relying on window.location without the values injected from anywhere makes it really hard to me), but there may be other ways to have this behavior implemented without modifying core's code.

@wardpeet
Copy link
Contributor

wardpeet commented Jul 3, 2019

@xavivars I'll be merging wardpeet/gatsby-plugin-static-site#4 and publishing a fix for this.

A demo: (page 5 has a canonical redirect)
https://static-asset-prefix--zen-wright-33c2d8.netlify.com/

@wardpeet
Copy link
Contributor

wardpeet commented Jul 4, 2019

I've just published @wardpeet/gatsby-plugin-static-site version 0.1.0. This should fix this issue. Feel free to reopen if it didn't fix all your issues.

Best way to get better static site support is to create an issue at the plugin itself. https://github.com/wardpeet/gatsby-plugin-static-site/issues/new

@blessanm86
Copy link

Anyone encountered this after using the above plugin?

@isi-gach
Copy link

isi-gach commented Oct 29, 2019

Any workaround working for the current version of GatsbyJS?

I tried:
https://github.com/wardpeet/gatsby-plugin-static-site

but it's not working for me. I raised an issue here:
wardpeet/gatsby-plugin-static-site#13

I also created a sample repo to reproduce the redirect issue:
https://github.com/isi-gach/gastby-static/tree/create-react-app

@ethagnawl
Copy link

@isi-gach Would you mind providing your take on the root issue (what you're expecting, what you're seeing, what you'd like to see)? A few of us in this thread have tried, but it might help to get a fresh take on it.

@isi-gach
Copy link

isi-gach commented Oct 29, 2019

hi @ethagnawl

I'm expecting that the browser URL doesn't change but I'm seeing the URL changing, in the following video the URL changes from /demo/index.html to /public/
https://www.youtube.com/watch?v=SxYbaDidnkY

That video was recorded using the sample repo that I have created:
https://github.com/isi-gach/gastby-static/tree/create-react-app

I'm trying to prevent the redirect using @wardpeet/gatsby-plugin-static-site but doesn't seem to work.

@xavivars
Copy link
Contributor

xavivars commented Nov 9, 2019

Hi @isi-gach @ethagnawl,

There are a couple of pull requests open into @wardpeet plug-in that should solve the problem you're mentioning.

While they get merged, you can use my fork instead

@isi-gach
Copy link

isi-gach commented Nov 9, 2019

Hi @xavivars
I tried the npm from your fork and now URL doesn't change but I got a white page:
https://www.youtube.com/watch?v=uNzk9UYVCxk

That video was recorded using the following sample repo replacing the wardpeet by plugin by yours:
https://github.com/isi-gach/gastby-static/tree/create-react-app

@vsolanogo
Copy link

how do i disable client side routing just for single page?

@HashemKhalifa
Copy link

You can use this

exports.onPreBootstrap = ({ store }) => {
  const { program } = store.getState()
  const filePath = path.join(program.directory, '.cache', 'production-app.js')

  const code = fs.readFileSync(filePath, {
    encoding: `utf-8`,
  })

  const newCode = code.replace(
    `const { pagePath, location: browserLoc } = window`,
    `const { pagePath } = window
    let { location: browserLoc } = window

    if (window.parent.location !== browserLoc) {
      browserLoc = {
        pathname: pagePath
      }
    }
  `
  )

  fs.writeFileSync(filePath, newCode, `utf-8`)
}

I'm not sure if it covers all the use cases the plugin covers, but it works fine for my case.

@zeorin
Copy link

zeorin commented Jan 8, 2021

I had a situation where I was using Apache to rewrite some URL paths.
I had to change matchPath to the rewritten path, and also rewrite the page-data/foo/bar/page-data.json URLs to match.

@xavivars
Copy link
Contributor

@zeorin , we're ussing assetPrefix, so all the assets are stored in a different domain (using the real gatsby URLs), so this isn't a problem for us.

@spiritual-coder
Copy link

Any fix for the broken links and 404 status code error issue with Client Side Routing. I'm stuck with this for two weeks around without getting any working solution.

@cortopy
Copy link

cortopy commented Mar 18, 2021

I also have an edge case where the routes are not known at build time. I've been reading this issue and the various solutions. None worked for me, probably because gatsby has changed a bit since this issue was created. I'm using Gatsby 3.10

For what I can see the main hurdle is the re-hydration stage where:

  • Gatsby will redirect the browser to the pathPrefix (if any) and pagePath set at build time. Obviously this is not good if they are not known at build time
  • Once the browser's location is in sync with what Gatsby expects, the loading of page data and resources begins, followed by the mounting of the React app

Some of these things happen outside of functions which could be overriden by plugins. Also, the pathname passed to some of these functions is hardcoded to be that of the browser's location.

My solution is the following patch-package diff:

diff --git a/node_modules/gatsby/cache-dir/production-app.js b/node_modules/gatsby/cache-dir/production-app.js
index baf20ed..94ee4cf 100644
--- a/node_modules/gatsby/cache-dir/production-app.js
+++ b/node_modules/gatsby/cache-dir/production-app.js
@@ -34,6 +34,12 @@ window.asyncRequires = asyncRequires
 window.___emitter = emitter
 window.___loader = publicLoader
 
+const { pagePath } = window
+
+const browserLoc = {
+  pathname: __BASE_PATH__ + (pagePath || '')
+}
+
 navigationInit()
 
 apiRunnerAsync(`onClientEntry`).then(() => {
@@ -70,7 +76,7 @@ apiRunnerAsync(`onClientEntry`).then(() => {
       return (
         <Location>
           {({ location }) => (
-            <EnsureResources location={location}>
+            <EnsureResources location={browserLoc}>
               {({ pageResources, location }) => {
                 const staticQueryResults = getStaticQueryResults()
                 return (
@@ -126,8 +132,6 @@ apiRunnerAsync(`onClientEntry`).then(() => {
     }
   }
 
-  const { pagePath, location: browserLoc } = window
-
   // Explicitly call navigate if the canonical path (window.pagePath)
   // is different to the browser path (window.location.pathname). But
   // only if NONE of the following conditions hold:

There are probably more elegant ways, but this is always going to be hacky and prone to break in future releases

If I can just give my two cents, this would be far easier if all this was encapsulated in some logic that plugins can override

@JimCaignard
Copy link

Did someone find a way to disable client side routing on Gatsby v3 ? I tried every solutions mentioned before but nothing works on my side :/

@logemann
Copy link

logemann commented Apr 27, 2021

same for me. I am trying to have a multi-domain v3 gatsby site with Netlify rewrite rules active. Doesnt seem possible at this point. Since this one is closed... do we need another ticket @wardpeet ?

@SimonKlausLudwig
Copy link

any update on this :( ?

@ThiagoMaia1
Copy link

Does anyone know of a way to do it on Gatsby 5?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: awaiting author response Additional information has been requested from the author type: question or discussion Issue discussing or asking a question about Gatsby
Projects
None yet