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

getInitialProps not working with history (back button)? #3065

Closed
tomsoderlund opened this issue Oct 10, 2017 · 56 comments · Fixed by #4153
Closed

getInitialProps not working with history (back button)? #3065

tomsoderlund opened this issue Oct 10, 2017 · 56 comments · Fixed by #4153

Comments

@tomsoderlund
Copy link

I have an app with a single page index.js, and I parse the query parameters in getInitialProps:

static async getInitialProps ({query}) {
	console.log('PAGE', query.screenId);
	try {
		const projectData = await ProjectLoader.loadProject(query);
		return projectData;
	}
	catch (err) {
		console.error(err);
	}
};

I use <Link href={pathname, query} prefetch> tags to build my links.

The screenId property is key to finding the right page to render.

However, when I use the back button in the browser to a page without screenId set, getInitialProps doesn’t return screenId:undefined as expected, but another value (as you can see in the console log):

getInitialProps not working with history

When I refresh the page, I get the right properties and see the correct results.

The “imaginary” screenId is actually a valid value, but I have no idea where this value comes from.

Why is this happening?

@tomsoderlund tomsoderlund changed the title not working with history (back button)? getInitialProps not working with history (back button)? Oct 10, 2017
@liweinan0423
Copy link
Contributor

This is interesting to me as I am facing a similar issue with "back button". Could you try using the latest version of next.js and share your repo if possible?

@anselmdk
Copy link

We're facing similar issues, any pointers would be appreciated.

@tomsoderlund
Copy link
Author

tomsoderlund commented Dec 22, 2017

This may not be the solution to the above problem, but the last few days I had a big “a-ha” moment with the <Link> component when you’re using custom routing:

You need both href and as properties, they both need to be valid, and they will most likely to be different from each other(!).

Example: <Link href={'/mynextjspage?articleId=' + articleId} as={'/articles/' + articleId}>

as will be the link as you want it to appear, while href will be a reference to a Page component in the /pagesfolder with parameters added as a query string.

@liweinan0423 @anselmdk Are you using custom routing?

@anselmdk
Copy link

I think our issue is that we use one central dispatcher (pages/dispatcher.js). The dispatcher decides which components to load, based on the url (we're loading data from an API behind the scenes).
This works perfectly until we start hitting the browser's "back" button. Then you're redirected to a random page. It seems like the issue is that our links look like this:

<Link
  href="/dispatcher"
  as="/page/about"
>
  <a>home</a>
</Link>

The href is always set to /dispatcher on all pages.

I've looked a little into other issues, and it seemed like the solution mentioned here would solve it. In short the idea is to append a unique query string, which would then make href look like something like this: /dispatcher?pretty=url1. Unfortunately that triggers another problem - the links just stop working (clicking them does nothing). I've isolated the issue by taking the code from https://github.com/zeit/next.js/tree/master/examples/parameterized-routing and making it run on one file only:

import React from 'react'
import Link from 'next/link'

export default class extends React.Component {
  static getInitialProps ({ query }) {
    return { id: query ? query.id : 'error'}
  }

  render () {
    return <div>
      <h1>My {this.props.id} blog post</h1>
      <ul>
        <li><Link href='/test?id=first' as='/test/first'><a>My first blog post</a></Link></li>
        <li><Link href='/test?id=second' as='/test/second'><a>My second blog post</a></Link></li>
        <li><Link href='/test?id=third' as='/test/last'><a>My last blog post</a></Link></li>
      </ul>
      <p>
        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua.
      </p>
    </div>
  }
}

Clicking any of the three links does nothing. When the query strings are removed from the links (so href only contains /test) it works fine again - unless the history part. A funny observation is that when after removing them you add them back in, and the component is hot reloaded, both links and history work - until you refresh the page.

I hope anyone's had similar issues and/or can point us into a direction for solving this.

@smdevdk
Copy link

smdevdk commented Dec 23, 2017

I am having the same issue..

@AshishKapoor
Copy link

Same here.

@EvanDylan
Copy link

Does anyone solved this problems?

@adamyonk
Copy link

@anselmdk / @tomsoderlund are you using a custom server to handle that sort of routing? Might be helpful to see that as well.

@anselmdk
Copy link

So, because nobody answered, neither here nor on Slack, we solved this issue by creating a custom version of link.js.
I think all we did was to change https://github.com/zeit/next.js/blob/canary/lib/link.js#L75
to Router[changeMethod](href + '?uri=' + as, as, { shallow }).
Feels a bit hacky, but it works :)

@mashaal
Copy link

mashaal commented Apr 2, 2018

@anselmdk can verify this works for the Link component, but if using Router.push/replace the issue still persists.

I updated the Router component with the following.

  push(url, as = url, options = {}) {
    return this.change('pushState', url + '?uri=' + as, as, options)
  }

  replace(url, as = url, options = {}) {
    return this.change('replaceState', url + '?uri=' + as, as, options)
  }

and you can use it like this Router.push('/', pathname)

gcpantazis pushed a commit to gcpantazis/next.js that referenced this issue Apr 13, 2018
Currently, using `as` will cause the router to think the URL is not
changing in the case where you're re-rendering the same page with a
different route. This would of course be an issue for custom servers
only.

This should be an invisible change for non-custom-server users, since
`as` is defaulted to `url` if not set.

This should resolve vercel#3065.
gcpantazis pushed a commit to gcpantazis/next.js that referenced this issue Apr 13, 2018
Currently, using `as` will cause the router to think the URL is not
changing in the case where you're re-rendering the same page with a
different route. This would of course be an issue for custom servers
only.

This should be an invisible change for non-custom-server users, since
`as` is defaulted to `url` if not set.

This should resolve vercel#3065.
@wallynm
Copy link

wallynm commented Aug 13, 2018

I'm getting same error, tried to update to 6.1.1 but won't fix.
Also tried to set useFileSystemPublicRoutes: false and didn't changed anything on the app.
I'm redirecting this way in my app but once i hit the back button the component don't fire the getInitialProps:

const cartId = 20;
Router.push(`/step2?cartId=${cartId}&hashId=asd`, `/next/${cartId}/asd`)

I've also tried with shallow but no success:

Router.push(`/step2?cartId=${cartId}&hashId=asd`, `/next/${cartId}/asd`, { shallow: true })

@HaNdTriX
Copy link
Contributor

Hey guys

I tried to reproduce your example but could not verify it.

Example:

Got you issue resolved in the meantime or did I do something wrong?

@Roy412
Copy link

Roy412 commented Aug 23, 2018

@HaNdTriX It works well inside App routing but not with external link.
If you navigate to external url(e.g. github) and click back button, you see getInitialProps is not called.

@Roy412
Copy link

Roy412 commented Aug 24, 2018

Guys, is there any cool solution or alternatives? It's a really block at my project.

class Profile extends React.Component {
    // Update the profile avatar and show updated avatar
    // Call api to update avatar at backend database
}
Profile.getInitialProps = () => {
    // Call api to get profile information
}

When I update the profile avatar and navigate to external url(e.g. terms pdf url) and click back button, it shows the previous avatar as getInitialProps is not called.
Router.replace() after updating Avatar, didn't help me.

@HaNdTriX
Copy link
Contributor

HaNdTriX commented Aug 24, 2018

@OscarBengtsson I've just tested it again. And it works from my point of view.

Navigate back inside root url (internally): getInitialProps is called on the client (client render).
Navigate back from external url (external): getInitialProps is called on the server (server render).

The initial request is always rendered on the server.

This is the intended behavior.

Edit: Nevertheless on chrome it works in Safari & Firefox not.

@HaNdTriX
Copy link
Contributor

HaNdTriX commented Aug 27, 2018

Ok I finally could reproduce the issue.

The issue is only existent in the browsers Safari & Firefox. These browsers use a feature called "bfcache" (back-forward cache). This means the html document is cached in memory. As soon as a user uses a back button coming from an external page, no request is made on the server. Instead the html document is rendered from the cache. Since getInitialProps already ran (before it was cached) it won't run again.

@timneutkens Do you have an idea about how to solve this and get all browsers inline?

@DontRelaX
Copy link

DontRelaX commented Aug 27, 2018

If you are experiencing issue that getInitialProps not invoked when user click back between same pages you can use next code (somewhere during bootstrap on frontend):

Router.beforePopState(({url, as}) => {
  Router.replace(url, as, {});
  return false;
});

timneutkens pushed a commit that referenced this issue Sep 3, 2018
Currently, using `as` will cause the router to think the URL is not changing in the case where you're re-rendering the same page with a different route. This would most likely be an issue for custom servers
which are using shallow routing.

This should be an invisible change for non-custom-server users, since `as` is defaulted to `url` if not set.

This should resolve #3065.
@wallynm
Copy link

wallynm commented Sep 17, 2018

Guys, finally i've better understood my problem. Im using Next 7.0 canary to see if latest updates work.

Im my project im using custom routing and my first URL is /start/:id/:hash
When i change route to next/:id/hash it works correct

Router.push({
    pathname: '/step2',
    query: { id: cartId, hash: 'asd' }
}, `/next/${id}/${hash}`)

Once i got back using chrome back button the route stop working as expected, the initialProps doesn't receive the params vars, They do work in step2 that i've redirect but once popstate gets called it doesnt receive any params option, because it was the first rendered page and next router didn't received any options as first render...

To solve this im splitting url, it's temporally until i found a way to solve this.

@DontRelaX
Copy link

@wallynm did you try my solution above? Looks very similar to what I faced.

@wallynm

This comment has been minimized.

@timneutkens
Copy link
Member

I'm wondering if you're using next export or not? If you're not there's definitely something else going on and we should investigate that further, as replacing on initial render is not the correct solution.

@wallynm
Copy link

wallynm commented Sep 20, 2018

I don't know for sure what is this config. Quick search and found this:
#604
I don't using anything like that, but let me explain a little about my current config:

I'm using custom routing integrated with Express:

const app = next({ dev, dir: './src' })
const handle = app.getRequestHandler()
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')
const compression = require('compression');

const render = function(req, res, page) {
  const parsedUrl = parse(req.url, true)
  const { pathname, query } = parsedUrl
  return app.render(req, res, page, query)
}

app.prepare().then(() => {
  const server = express()
  if (process.env.NODE_ENV === "production") {
    server.use(compression());
  }

  server.get('/start/:id/:hash', (req, res, next) => render(req, res, '/step1'))
  server.get('/next/:id/:hash', (req, res, next) => render(req, res, '/step2'))
})

@bautistaaa
Copy link

having this issue with ^8.0.0

this work around appears to work but it does a full page reload which isn't ideal, any other solutions?

    Router.beforePopState(({ as }) => {
      window.location.href = as;
      window.location.reload();
      return false 
     // returning true or false here doesnt seem to matter, new signature requires a boolean to be returned
    });

@cotyhamilton
Copy link

I was having this error in my getInitialProps function at a line that used ctx.req.url

Error:
TypeError: undefined is not an object (evaluating 'ctx.req.url')

I changed this line to use a url param, ctx.query.user (I have a [user]/index.js page) and I no longer receive this error 🤷‍♂️

before:
let username = ctx.req.url.split('/')[1];

after:
let username = ctx.query.user;

I haven't been using next.js for very long and I have no idea why this is happening. Dumping the ctx object contains everything it should when hitting the back button.

@loganpowell
Copy link

loganpowell commented Aug 15, 2019

I'm getting this same behavior (internal routing - within pages inside my next.js app - work, but once navigating away - or even to a static asset - breaks). I am using export.

Something to note. I also didn't have this problem when testing with github pages, but this showed up for us when we needed to move it to production in aws s3 (I pitched now, but we need to use govcloud)

@loganpowell
Copy link

loganpowell commented Aug 15, 2019

If anyone runs into this issue in the future, I found a not-so-pretty solution.

I'm not sure why next export doesn't work in s3, but does work in github pages. However, if you are using aws with next export and you're running into 404 pages when either refreshing any page or navigating back from an external page to your next.js static site, I've found the following works:

If you add .html to your as= prop in any component using next/link for example:

Diff

import { withRouter } from 'next/router'
import Link from 'next/link'

const custom_link = ({ href, router, children, name, as }) => 
-   <Link prefetch href={href} as={as} passHref>
+   <Link prefetch href={href} as={as + ".html"} passHref>
      {children}
    </Link>

export const CustomLink = withRouter(custom_link)

This works as it forces the server to serve the html file for the page instead of referring to the router which has lost its context... from what I gather.

Hope this helps someone who's in a bind and needs a quick fix. Not an ideal solution, but eh...

@xe4me
Copy link

xe4me commented Oct 5, 2019

Why is this closed ? :(

@codefist
Copy link

codefist commented Oct 9, 2019

just encountered this. this should not be closed

@PerlBug
Copy link

PerlBug commented Oct 21, 2019

Same issue here on Safari 13 (Technology Preview)

@arjunmehta
Copy link

arjunmehta commented Nov 4, 2019

Also experiencing this with Safari. Chrome works as expected.

If anything the different behaviors in the two browsers should be treated as an issue in and of itself (ie: The point @HaNdTriX made about bfcache)

@utsavm9
Copy link

utsavm9 commented Dec 26, 2019

This issue is in Firefox, Chrome when using Next 9.1.6. This needs to be reopened.

@utsavm9
Copy link

utsavm9 commented Dec 27, 2019

This hack works for now, but please let me know if anyone has a good solution or cause for this problem.

static async getInitialProps(context) {

    //Hack: If this code is executing in client-side, just reload.
    if (process.browser) {
        history.go();
    }

    ...
    return { ... };
}

@fabb
Copy link
Contributor

fabb commented Feb 14, 2020

Is there a workaround that does NOT cause SSR? We‘d like to keep unnecessary SSR low because of backend resources.

@jooj123
Copy link

jooj123 commented Mar 2, 2020

This is still an issue for me - for all browsers.
I am using Router.push to navigate to pages, so I think thats possibly where the issue lies ?

@tsmith123
Copy link

Getting issue in Chrome and Next 9.2.1. getInitialProps does not refetch data when using the back button

@kush-agra
Copy link

kush-agra commented Mar 9, 2020

Issue exists in firefox, experienced when using the forward button too, currently using the

#3065 (comment)

hack to get around it but would like a better solution, maybe one of the Router.events can be of some use? will investigate too

Edit: used context to get around the issue completely as it fell into my use case, picking values from context while transitioning and taking value from query and setting into context to use when page is reloaded or back/forward buttons pressed

@beamercola
Copy link

Issue in Safari still

@f2hard3
Copy link

f2hard3 commented Mar 11, 2020

Upgrading to 9.3.0 and changing getIntialProps to getServerSideProps solved the problem.

@kush-agra
Copy link

It really did solve it, thanks :)

@beamercola
Copy link

Awesome, worked for me but getServerSideProps didn't do anything, but getInitialProps seems to work now with back button

@visualcookie
Copy link

I'm running a legacy project on 9.1.5, and really can't update right now, as this would break a lot. Tried the quick fix from #3065 (comment) but still doesn't work. He doesn't refetch the data from _app.tsx.

@timneutkens
Copy link
Member

I'm running a legacy project on 9.1.5, and really can't update right now

There haven't been breaking changes in the latest releases.

@shrugs
Copy link

shrugs commented Aug 3, 2020

for searchers finding this issue, here's another possible cause of the back button not working, getInitialProps or otherwise:

i was using window.history.replaceState(null, '', as); to change the url without triggering next's shallow routing (which causes a react rerender), but this apparently doesn't play well with how next manages the url. replacing that code with shallow rendering (and then refactoring my code to allow those re-renders) solved my issue and got the back button to work again

@sphilee
Copy link
Contributor

sphilee commented Oct 13, 2020

In app.ts, execute the following statement on componentDidMount.

// app.ts
this.props.router.beforePopState(({ as, options }) => {
      if (options.shallow) {
        this.props.router.replace(as)
        return false
      }
      return true
    })

@mankenconde
Copy link

Upgrading to 9.3.0 and changing getIntialProps to getServerSideProps solved the problem.

This worked for me! Thank you @f2hard3 !

@jpamarohorta
Copy link

Hello, I'm having this exact same issue with Router.push and an componentDidMount kind of useEffect. What is the recommended solution for a client-side rendering flow?

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 28, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.