gatsbyjs / gatsby

The best React-based framework with performance, scalability and security built in.
https://www.gatsbyjs.com
MIT License
55.27k stars 10.31k forks source link

Disable Client-Side Routing? #4337

Closed TuckerWhitehouse closed 5 years ago

TuckerWhitehouse commented 6 years ago

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

xavivars commented 5 years ago

To me, it worked with all three things: gatsby plugin static site + assetPrefix + disabling "navigate" as shown above

wardpeet commented 5 years ago

Seems like I still have a hard time understanding what is needed here. I could just fix the issue with gatsby-plugin-static-site & assetPrefix.

ethagnawl commented 5 years ago

@wardpeet Do you have a link to a demo which uses gatsby-plugin-static?

wardpeet commented 5 years ago

@ethagnawl sorry to keep you waiting.

I made a demo: https://github.com/wardpeet/gatsby-demos/tree/static-asset-prefix

site is live: https://zen-wright-33c2d8.netlify.com/

xavivars commented 5 years ago

As expected (and anticipated by @TuckerWhitehouse and @ethagnawl), a fragile solution like the one provided in https://github.com/gatsbyjs/gatsby/issues/4337#issuecomment-453244611 doesn't work anymore, due to a change in Gatsby 2.9.2, for several reasons:

The first one, solvable, is that this line window.page.path = window.location.pathname; needs to be replaced with window.pagePath = window.location.pathname; in order to avoid the change in the URL client side.

But that has undesired side-effects: pagePath is set with the wrong path, and page-data.json is not loaded anymore (as it relies on the original path of the page, and not the one where it's finally rendered)

https://github.com/gatsbyjs/gatsby/commit/49fd769f695ccfa6e990e3eaae7c886f073db19b#diff-2d21ea42ec874a0988977e57b17251aa

It seems the only option to make this work now would be to really introduce a variable like __disable_client_side_routing__ or, at least, __disable_client_side_canonical_redirect__ to shortcircuit this condition: https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/cache-dir/production-app.js#L69

@wardpeet: do you see any problem on introducing such config variable?

wardpeet commented 5 years ago

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 commented 5 years ago

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 https://github.com/gatsbyjs/gatsby/pull/15173).

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

xavivars commented 5 years ago

@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

https://github.com/gatsbyjs/gatsby/pull/15180

xavivars commented 5 years ago

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 commented 5 years ago

@xavivars I'll be merging https://github.com/wardpeet/gatsby-plugin-static-site/pull/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 commented 5 years ago

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 commented 5 years ago

Anyone encountered this after using the above plugin?

isi-gach commented 5 years ago

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: https://github.com/wardpeet/gatsby-plugin-static-site/issues/13

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

ethagnawl commented 5 years ago

@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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 4 years ago

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

HashemKhalifa commented 4 years ago

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 commented 3 years ago

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 commented 3 years ago

@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 commented 3 years ago

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 commented 3 years ago

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:

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

any update on this :( ?

ThiagoMaia1 commented 11 months ago

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

ilyha81 commented 3 months ago

Still no solution?

ThiagoMaia1 commented 3 months ago

@ilyha81

This sort of worked for me. It's been a while since I implemented it, so I can't remember the details, but see if it helps you on the right track.

// gatsby-browser.ts

export const onClientEntry = () => {
  if (isExperimentPath(window.pagePath as Pathname)) {
    // Remove the experiment path prefix from the page path so that Gatsby doesn't internally redirect to the canonical URL.
    window.pagePath = window.pagePath?.replace(/\/experiment\/[^/]+/g, "")
  }
}

In my case, the goal is to have experiments without losing speed and without page flickering, so I generate different variants of the page, and use the code above to prevent the redirect. And then, I use Gatsby's page context based on different conditions so that when rehydration happens it has the right variant.