Next.js: Shallow routing while using withRouter not working

Created on 12 Jul 2020  路  9Comments  路  Source: vercel/next.js

Bug report

Describe the bug

Using withRouter as a wrapper, shallow routing doesn't seem to be working.

To Reproduce

I currently use this method to change the route:

        this.props.router.push({
            pathname: currentPath,
            query: currentQuery,
        });

And couldn't figure where to put the shallow flag. So I switched to the method mentioned in the docs:
this.props.router.push('/post/[pid]?hello=123', '/post/abc?hello=123', { shallow: true })

So I did that manually, but I started getting 404s.

http://localhost:3000/_next/static/development/pages/search/%5Btype%5D/%5Bcat%5D/%5Barea%5D.js net::ERR_ABORTED 404 (Not Found)

decoded:
"http://localhost:3000/_next/static/development/pages/search/[type]/[cat]/[area].js"

I tried using :type instead of [type] but it also didn't work.

This is how it's configured on the server:

                if ('/search/:type/:cat/:area' === route.path) {
                    return app.render(req, res, route.page);
                }

Folder Structure:
/pages/search/index.js

Expected behavior

It should not reload the page while changing the route, that's the main thing I'm trying to accomplish.

System information

  • OS: [Ubuntu 18.04]
  • Browser (if applies) [chrome]
  • Version of Next.js: [9.2.1]
  • Version of Node.js: [12.18.0]

Additional context

I'm implementing SSR pagination, and I'm planning to use shallow routing to make page changes happen on the client instead of the server. Meaning achieve SSR of first load only, keep user in without refreshing.

This is might be related #4545

please add a complete reproduction

All 9 comments

This works as follows:

server.js

server.get('/users/:username', (req, res) => {
  return app.render(req, res, '/users/[username]', req.params)
})

usage on a page

props.router.push('/users/[username]?counter=10', '/users/foo?counter=10', { shallow: true });

Hello @jamesmosier
I tried what you mentioned above:

        server.get('/search/:type/:cat/:area', (req, res) => {
                 console.log("reached here...");   // this gets logged
                return app.render(
                    req,
                    res,
                    '/search/[type]/[cat]/[area]',
                    req.params
                );
        });

But I'm getting 404s, the page is not there now!

I managed to get those logs:

_next/static catchall' -> generateRoutes -> parsedUrl Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?ts=1594623181340',
  query: [Object: null prototype] { ts: '1594623181340' },
  pathname: '/_next/static/development/pages/_app.js',
  path: '/_next/static/development/pages/_app.js?ts=1594623181340',
  href: '/_next/static/development/pages/_app.js?ts=1594623181340'
}
_next/static catchall' -> generateRoutes -> params { path: [ 'development', 'pages', '_app.js' ] }
_next/static catchall' -> generateRoutes -> parsedUrl Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?ts=1594623181340',
  query: [Object: null prototype] { ts: '1594623181340' },
  pathname: '/_next/static/runtime/webpack.js',
  path: '/_next/static/runtime/webpack.js?ts=1594623181340',
  href: '/_next/static/runtime/webpack.js?ts=1594623181340'
}
_next/static catchall' -> generateRoutes -> params { path: [ 'runtime', 'webpack.js' ] }
_next/static catchall' -> generateRoutes -> parsedUrl Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?ts=1594623181340',
  query: [Object: null prototype] { ts: '1594623181340' },
  pathname: '/_next/static/runtime/main.js',
  path: '/_next/static/runtime/main.js?ts=1594623181340',
  href: '/_next/static/runtime/main.js?ts=1594623181340'
}
_next/static catchall' -> generateRoutes -> params { path: [ 'runtime', 'main.js' ] }
_next/static catchall' -> generateRoutes -> parsedUrl Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?ts=1594623181340',
  query: [Object: null prototype] { ts: '1594623181340' },
  pathname: '/_next/static/development/dll/dll_d6a88dbe3071bd165157.js',
  path: '/_next/static/development/dll/dll_d6a88dbe3071bd165157.js?ts=1594623181340',
  href: '/_next/static/development/dll/dll_d6a88dbe3071bd165157.js?ts=1594623181340'
}
_next/static catchall' -> generateRoutes -> params { path: [ 'development', 'dll', 'dll_d6a88dbe3071bd165157.js' ] }
_next/static catchall' -> generateRoutes -> parsedUrl Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?ts=1594623181340',
  query: [Object: null prototype] { ts: '1594623181340' },
  pathname: '/_next/static/development/_buildManifest.js',
  path: '/_next/static/development/_buildManifest.js?ts=1594623181340',
  href: '/_next/static/development/_buildManifest.js?ts=1594623181340'
}
_next/static catchall' -> generateRoutes -> params { path: [ 'development', '_buildManifest.js' ] }
_next/static catchall' -> generateRoutes -> parsedUrl Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: null,
  query: [Object: null prototype] {},
  pathname: '/_next/static/development/dll/dll_d6a88dbe3071bd165157.js.map',
  path: '/_next/static/development/dll/dll_d6a88dbe3071bd165157.js.map',
  href: '/_next/static/development/dll/dll_d6a88dbe3071bd165157.js.map'
}
_next/static catchall' -> generateRoutes -> params { path: [ 'development', 'dll', 'dll_d6a88dbe3071bd165157.js.map' ] }
_next/static catchall' -> generateRoutes -> parsedUrl Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: null,
  query: [Object: null prototype] {},
  pathname: '/_next/static/runtime/webpack.js.map',
  path: '/_next/static/runtime/webpack.js.map',
  href: '/_next/static/runtime/webpack.js.map'
}
_next/static catchall' -> generateRoutes -> params { path: [ 'runtime', 'webpack.js.map' ] }
_next/static catchall' -> generateRoutes -> parsedUrl Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: null,
  query: [Object: null prototype] {},
  pathname: '/_next/static/chunks/0.js',
  path: '/_next/static/chunks/0.js',
  href: '/_next/static/chunks/0.js'
}
_next/static catchall' -> generateRoutes -> params { path: [ 'chunks', '0.js' ] }
_next/static catchall' -> generateRoutes -> parsedUrl Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: null,
  query: [Object: null prototype] {},
  pathname: '/_next/static/runtime/main.js.map',
  path: '/_next/static/runtime/main.js.map',
  href: '/_next/static/runtime/main.js.map'
}
_next/static catchall' -> generateRoutes -> params { path: [ 'runtime', 'main.js.map' ] }
_next/static catchall' -> generateRoutes -> parsedUrl Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: null,
  query: [Object: null prototype] {},
  pathname: '/_next/static/chunks/0.js.map',
  path: '/_next/static/chunks/0.js.map',
  href: '/_next/static/chunks/0.js.map'
}
_next/static catchall' -> generateRoutes -> params { path: [ 'chunks', '0.js.map' ] }
_next/static catchall' -> generateRoutes -> parsedUrl Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: null,
  query: [Object: null prototype] {},
  pathname: '/_next/static/development/pages/_app.js.map',
  path: '/_next/static/development/pages/_app.js.map',
  href: '/_next/static/development/pages/_app.js.map'
}
_next/static catchall' -> generateRoutes -> params { path: [ 'development', 'pages', '_app.js.map' ] }
_next/static catchall' -> generateRoutes -> parsedUrl Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: null,
  query: [Object: null prototype] {},
  pathname: '/_next/static/development/pages/_error.js',
  path: '/_next/static/development/pages/_error.js',
  href: '/_next/static/development/pages/_error.js'
}
_next/static catchall' -> generateRoutes -> params { path: [ 'development', 'pages', '_error.js' ] }
_next/static catchall' -> generateRoutes -> parsedUrl Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: null,
  query: [Object: null prototype] {},
  pathname: '/_next/static/development/pages/_error.js.map',
  path: '/_next/static/development/pages/_error.js.map',
  href: '/_next/static/development/pages/_error.js.map'
}
_next/static catchall' -> generateRoutes -> params { path: [ 'development', 'pages', '_error.js.map' ] }

Could you provide a full repository showing this issue?

I have the exact same file structure and am able to get this to work.

pages/search/[type]/[cat]/[area].js

<button
  type="button"
  onClick={() => {
    props.router.push('/search/[type]/[cat]/[area]?counter=10', '/search/foo/bar/baz?counter=10', { shallow: true });
  }}
>
  go
</button>

server.js

server.get('/search/:type/:cat/:area', (req, res) => {
  return app.render(req, res, '/search/[type]/[cat]/[area]', req.params)
})

url http://localhost:3000/search/foo/bar/baz and clicking button shallow routes and add a ?counter=10 query string suffix.

I'll try to prepare a separate repo tonight, hopefully.
But just to make sure that your repro is the same as mine, do you have a search folder in pages dir with index.js?
/pages/search/index.js
This means there are no [type].js nor [cat].js nor [area].js pages.

also the params aren't in english, they're in arabic, so decoding/encoding might has something to do with that.

Anyways. I'll try to prepare the repo ASAP.

Hello @jamesmosier
You can find the repo here https://github.com/omar-dulaimi/nextjs-shallow-routing-repro
currently the search page won't render, you can find a comment on the server how I currently get it to render.

Please let me know if there's anything I could change.

This should work:

  server.get('/search/:type/:cat/:area', (req, res) => {
    return app.render(req, res, '/search', {
      ...req.params,
      ...req.query,
    });
  });
props.router.push(
  '/search?type=foo&cat=bar&area=baz&counter=10',
  '/search/foo/bar/baz?counter=10',
  { shallow: true }
);

Hey @jamesmosier
This seems promising, like it rendered the page it actually added a query param without reloading the page.
But I noticed that it does nothing with consequent clicks, could you share your working repo please?

import React from "react";
import Router, { withRouter } from "next/router";

class SearchPage extends React.Component {
  constructor(props) {
    try {
      super(props);
      this.state = {
        counter: 0,
      };
    } catch (e) {
      console.log(
        "pages => search => index.js => SearchPage -> constructor -> e",
        e
      );
    }
  }

  componentDidMount() {
  }

  render() {
    const { counter } = this.state;
    return (
      <button
        type="button"
        onClick={() => {
          Router.push(
            `/search?type=foo&cat=bar&area=baz&counter=${counter}`,
            `/search/foo/bar/baz?counter=${counter + 1}`,
            { shallow: true }
          );
        }}
      >
        go
      </button>
    );
  }
}

export default withRouter(SearchPage); 

My working test case just displays what you mentioned originally in this issue that shallow rendering works with a custom server and dynamic routes. It:

added a query param without reloading the page

which should resolve this issue.


As for your other issue, I'm not sure what that tests case is supposes to do.

`/search?type=foo&cat=bar&area=baz&counter=${counter}`,
`/search/foo/bar/baz?counter=${counter + 1}`,

would be

`/search?type=foo&cat=bar&area=baz&counter=0`,
`/search/foo/bar/baz?counter=1`,

which would be different counter values

It works great now.
Thanks a lot for your time and effort.
@jamesmosier

Was this page helpful?
0 / 5 - 0 ratings

Related issues

formula349 picture formula349  路  3Comments

lixiaoyan picture lixiaoyan  路  3Comments

kenji4569 picture kenji4569  路  3Comments

jesselee34 picture jesselee34  路  3Comments

YarivGilad picture YarivGilad  路  3Comments