-
Notifications
You must be signed in to change notification settings - Fork 27k
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
withRouter doesn't parse query on static website #4804
Comments
I have met the same question with you. actually, after exported, you can not use exportPathMap: function(defaultPathMap) {
return {
'/': { page: '/' },
'/about': { page: '/about' },
'/readme.md': { page: '/readme' },
'/p/hello-nextjs': { page: '/post', query: { title: 'hello-nextjs' } },
'/p/learn-nextjs': { page: '/post', query: { title: 'learn-nextjs' } },
'/p/deploy-nextjs': { page: '/post', query: { title: 'deploy-nextjs' } }
}
} |
@wslx520 Thanks for clarifying, I understand it better now. I need to pass a dynamic "id" to display a different entity each time. I can create a higher level component to wrap my layout component and parse the query params by myself, is it a valid way to go? |
@saginadir I think it's valid. sometimes I just write a function for getting query from the |
@wslx520 Awesome, thanks for the help, I implemented something similar that will fetch the query params, called it {myWithRouter} and using it to wrap my component instead of {withRouter} |
@wslx520, @saginadir I was debugging the same issue before I came across this post. Maybe someone from the Next.js team could explain if this is how it should work? Maybe there a way to force the Router to refresh so that it reads the query? |
@eigilsagafos I am not sure how the next.js team expect to solve this, but my solution was this: // Our router to override the query
// missing query when deploying to static export
export const ourWithRouter = (Component) => {
return withRouter(({router, ...props}) => {
// taking the query from router and overriding the query
router.query = globalRouter.match(router.asPath).query
return <Component {...props} router={router} />
})
} globalRouter.match function parses the router.asPath property and returns an object which contains the query. In my case I use 3rd party next-router library which gives me slugified URLs. But if you use the regular "?key=value" query, then maybe you can just use something like: Then you just use |
@saginadir Where do you obtain the |
I Apologize for the late response as I wrote
Depends on what you are trying to do - you may need to have custom code on how you parse the "router.asPath" |
Here is what I ended up doing: import { withRouter } from 'next/router';
/**
* Our router to override the missing query when deploying to static export
* This is required for page components that need access to the router
*
* @param {React.Component} Component
*
* @return {React.Component}
*/
export const withPageRouter = (Component) => {
return withRouter(({ router, ...props }) => {
// split at first `?`
const searchParams = new URLSearchParams(router.asPath.split(/\?/)[1]);
const query = {};
for (const [key, value] of searchParams) {
query[key] = value;
}
// replace the empty query
router.query = query;
return (<Component {...props} router={router} />);
});
}; Very similar to what @saginadir did but without the external dependency. This works identically to |
Summary: When going to /results page from a built nextjs application, getInitialProps and the likes are ignored. That meant that refreshing /results would return Error because the query params were not taken into account. I used the solution from this comment vercel/next.js#4804 (comment), and it works pretty well. Caveat is that we show for a brief moment a sort of broken state. That should be improved.
Summary: When going to /results page from a built nextjs application, getInitialProps and the likes are ignored. That meant that refreshing /results would return Error because the query params were not taken into account. I used the solution from this comment vercel/next.js#4804 (comment), and it works pretty well. Caveat is that we show for a brief moment a sort of broken state. That should be improved.
Summary: When going to /results page from a built nextjs application, getInitialProps and the likes are ignored. That meant that refreshing /results would return Error because the query params were not taken into account. I used the solution from this comment vercel/next.js#4804 (comment), and it works pretty well. Caveat is that we show for a brief moment a sort of broken state. That should be improved.
Summary: When going to /results page from a built nextjs application, getInitialProps and the likes are ignored. That meant that refreshing /results would return Error because the query params were not taken into account. I used the solution from this comment vercel/next.js#4804 (comment), and it works pretty well. Caveat is that we show for a brief moment a sort of broken state. That should be improved.
Summary: When going to /results page from a built nextjs application, getInitialProps and the likes are ignored. That meant that refreshing /results would return Error because the query params were not taken into account. I used the solution from this comment vercel/next.js#4804 (comment), and it works pretty well. Caveat is that we show for a brief moment a sort of broken state. That should be improved.
Summary: When going to /results page from a built nextjs application, getInitialProps and the likes are ignored. That meant that refreshing /results would return Error because the query params were not taken into account. I used the solution from this comment vercel/next.js#4804 (comment), and it works pretty well. Caveat is that we show for a brief moment a sort of broken state. That should be improved.
Above solution is really good but I’ve had some issues So there is another (and shorter) version using library
|
@chromale hmm the |
Thanks for your snippet @james2doyle it would be awesome if that little example was in the docs |
This doesn't work on IE11 |
This feature is supported out-of-the-box in Next.js 9! Please read the blog post to learn more. tl;dr after hydration, Next.js will trigger an update with the query string parameters. |
Migrated to Next.js 9 to solve this problem, but that brought it's own problem to sour my joy! Deciding to export pages as HTML files in the root of the |
Thank you @timneutkens, you are a life saver! I need to start contributing to Next.js soonest, even though I have no idea where to start |
@Timer I am using
for a given |
In the meantime, using the above ideas here is what I came up with: import { withRouter } from 'next/router';
import React from 'react';
export const withPageRouter = ComposedComponent => {
const WithPageRouteWrapper = withRouter(({ router, ...props }) => {
router.query = [
...new URLSearchParams((router.asPath || '').split(/\?/)[1]).entries()
].reduce((q, [k, v]) => Object.assign(q, { [k]: v }), {});
return <ComposedComponent {...props} router={router} />;
});
WithPageRouteWrapper.getInitialProps = ComposedComponent.getInitialProps;
WithPageRouteWrapper.origGetInitialProps =
WithPageRouteWrapper.origGetInitialProps;
if (process.env.NODE_ENV !== 'production') {
const name =
ComposedComponent.displayName || ComposedComponent.name || 'Unknown';
WithPageRouteWrapper.displayName = `withPageRouter(${name})`;
}
return WithPageRouteWrapper;
}; |
This wont work if your component uses You can fix that by import { withRouter } from 'next/router';
export const withPageRouter = Component => {
const routerWrapper = withRouter(({ router, ...props }) => {
router.query = [
...new URLSearchParams((router.asPath || '').split(/\?/)[1]).entries()
].reduce((q, [k, v]) => Object.assign(q, { [k]: v }), {});
return <Component {...props} router={router} />;
});
routerWrapper.getInitialProps = Component.getInitialProps;
return routerWrapper;
}; |
Ah @Rameshv you are right. Thanks for the heads up! |
Alright following @Rameshv advice, plus looking at the source code at https://github.com/zeit/next.js/blob/canary/packages/next/client/with-router.tsx , I've updated the code above (at #4804 (comment)) to something that I think should be bug-free now. |
Extending on @cansin's example from #4804 (comment), I ended up adding a couple of enhancements:
import React from 'react';
import { withRouter as withNextRouter, useRouter as useNextRouter } from 'next/router';
/**
* Given a string such as:
*
* https://example.com/foo?bar=zip&name=Sam
*
* Will return:
*
* {
* bar: 'zip',
* name: 'Sam',
* }
*/
const queryFromUrl = url => {
const [, ...queryStrings] = url.split('?');
const queryString = queryStrings.join('?');
const query = {};
for (let [key, value] of new URLSearchParams(queryString).entries()) {
query[key] = value;
}
return query;
};
const extractQueryFromRouter = router => ({
...queryFromUrl(router.asPath),
...router.query,
});
/**
* Provide a router provider which ensures router.query is always correctly
* hydrated on the first render even when statically optimised to avoid [this
* caveat from the docs](https://nextjs.org/docs/routing/dynamic-routes):
*
* > Pages that are statically optimized by Automatic Static Optimization will
* > be hydrated without their route parameters provided, i.e `query` will be an
* > empty object (`{}`).
*
* Also injects a `.query` paramter to the `context` in `getInitialProps` making
* it more useful than the existing `.asPath`
*
* Usage is identical to `import { withRouter } from 'next/router';
*
* Modified from https://github.com/zeit/next.js/issues/4804#issuecomment-541420735
*/
export const withRouter = ComposedComponent => {
const WithPageRouteWrapper = withNextRouter(({ router, ...props }) => {
router.query = extractQueryFromRouter(router);
return <ComposedComponent {...props} router={router} />;
});
if (ComposedComponent.getInitialProps) {
WithPageRouteWrapper.getInitialProps = (context, ...args) => {
context.query = extractQueryFromRouter(context);
return ComposedComponent.getInitialProps(context, ...args);
};
}
if (process.env.NODE_ENV !== 'production') {
const name =
ComposedComponent.displayName || ComposedComponent.name || 'Unknown';
WithPageRouteWrapper.displayName = `withPageRouter(${name})`;
}
return WithPageRouteWrapper;
};
/**
* Provide a router hook which ensures router.query is always correctly hydrated
* on the first render even when statically optimised to avoid [this caveat from
* the docs](https://nextjs.org/docs/routing/dynamic-routes):
*
* > Pages that are statically optimized by Automatic Static Optimization will
* > be hydrated without their route parameters provided, i.e `query` will be an
* > empty object (`{}`).
*
* Usage is identical to `import { useRouter } from 'next/router';
*/
export const useRouter = () => {
const router = useNextRouter();
router.query = extractQueryFromRouter(router);
return router;
}; |
This comment has been minimized.
This comment has been minimized.
This demonstrates a dynamically router page for individual notebooks. The notebook name is a variable that is part of the URL. The page also takes the query parameters as initialStaticProps. To read the query parameters, we use a custom version of the useRouter hook; see vercel/next.js#4804 (comment)
This demonstrates a dynamically router page for individual notebooks. The notebook name is a variable that is part of the URL. The page also takes the query parameters as initialStaticProps. To read the query parameters, we use a custom version of the useRouter hook; see vercel/next.js#4804 (comment)
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. |
Bug report
Describe the bug
I'm using withRouter to get the query data from the URL, and when I export to a static website, the router.query prop is empty
To Reproduce
Steps to reproduce the behavior, please provide code snippets or a repository:
Expected behavior
for router.query to contain
Screenshots
not static export:
static export:
System information
The text was updated successfully, but these errors were encountered: