93

I got a problem with my dynamic route. It look like this

[lang]/abc

I am trying to get query value from [lang] but when I using useRouter/withRouter i got query during 2-3 render of page ( on first i got query.lang = undefined ). its possible to get in 1 render or use any technique ?

enter image description here

10 Answers 10

181

I found something:

isReady: boolean - Whether the router fields are updated client-side and ready for use. Should only be used inside of useEffect methods and not for conditionally rendering on the server. https://nextjs.org/docs/api-reference/next/router#router-object

And the code would be like:

const router = useRouter();
useEffect(()=>{
    if(!router.isReady) return;

    // codes using router.query

}, [router.isReady]);
Sign up to request clarification or add additional context in comments.

4 Comments

This should be the accepted answer. This is exactly what isReady was introduced for.
This worked like a charm compared to other tricky solutions that could have weird side effects! Thanks
Just discovered this by console logging the useRouter function. This solves a lot of resources instead of doing SSR! I would highly recommend this of SSR rendering just path params.
It says "Should only be used inside of useEffect methods", does it mean that we can't use it with componentDidUpdate? I can't make it work in class-based components.
69

It's impossible to get the query value during the initial render.

Statically optimized pages are hydrated without the route parameters, so the query is an empty object ({}).

Next.js will populate the query after the page has been hydrated.

Next.js 10.0.5 and up

To determine if the route params are ready, you can use router.isReady inside a useEffect hook. For an example, see the answer provided by @doxylee.

Before Next.js 10.0.5

At first render of a dynamic route router.asPath and router.route are equal. Once query object is available, router.asPath reflects it.

You can rely on the query value within a useEffect hook after asPath has been changed.

const router = useRouter();

useEffect(() => {
  if (router.asPath !== router.route) {
    // router.query.lang is defined
  }
}, [router])

GitHub Issue - Add a "ready" to Router returned by "useRouter"

2 Comments

You should be careful with this approach because it's buggy because asPath and route can be equal, which doesn't reflect the actual readiness of the router. You can rely on this only when your URL has always a query.
Relevant documentation about automatic static optimization and the query object: nextjs.org/docs/advanced-features/….
14

In NextJS 9+, one way to ensure route parameters are immediately available for page components is to get them from the context arg passed to getServerSideProps() and pass to the component as props.

For a page like [id].js,

export function getServerSideProps(context) {
  return {
    props: {params: context.params}
  };
}

export default ({params}) => {
  const {id} = params;
  return <div>You opened page with {id}</div>;
};

7 Comments

Tried this on next 9.4.4, and I'm getting an error because context.props is undefined, which can't be serialized to JSON. Was hoping it would contain the query instead.
context.props context has not props, look better at the example. Your page props will receive what you return as { props: {} } from getServerSideProps.
Hi @Mycolaos, the answer was actually leveraging context.params - not context.props. Just reverified the approach on Next 10. Reference: nextjs.org/docs/basic-features/…
Use of getServerSideProps instructs Next.js to pre-render a page on the server side on each request. Means it isn't statically optimised and cached. Use it only if you're wiling to sacrifice performance. E.g. if you must fetch data before first render.
I would not recommend this for just checking params this will cause a SSR which vercel recommends to use sparingly. Use the useRouter: isReady boolean
|
9

This is a great question and one that took a few days for me to figure out what the best approach is.

I have personally found three viable solutions to the problem of validating dynamic route path params or even just route path params in general.

The three solutions are

  1. SSR (don't recommend) [Next >= 10]
  2. useRouter
  3. Middleware [Next 12 required]

In my examples a will use a route that requires a reset-token or it should be redirected.

SSR Firstly server side rending with getServerSideProps. Vercel recommends to use SSR as a last resort and I would highly recommend not using SSR when able (time to byte & cost).

We suggest trying Incremental Static Generation or Client-side Fetching and see if they fit your needs. https://vercel.com/blog/nextjs-server-side-rendering-vs-static-generation

But in the case that you do, say there is some server side api validation call you require to validate the query param.

export const getServerSideProps = async (context) => {
  const { token } = context.query;

  if (!token) {
    return {
      redirect: {
        permanent: false,
        destination: "/",
      }
    }
  }

  return {
    props: {}
    // props: { token }
    // You could do this either with useRouter or passing props
  }
}

useRouter Secondly the easiest useRouter. When I first did this I came across the problem when nextjs/react hydrates there will be a point when the query params are null. Luckily useRouter has isReady!

import Router, { useRouter } from "next/router";

const { query, isReady } = useRouter();

useEffect(() => {
  if (!isReady) return;
  if (!query.token) {
    Router.push("/")
  }
}, [isReady])

Middleware now this is my personal favourite as it seperates the functionality in a clean way imo. I found this based of a vercel example. I would highly recommend reading through a bunch of these to find best practices. https://github.com/vercel/examples/

import { NextResponse, NextRequest } from 'next/server'

export async function middleware(req) {
    const { pathname, searchParams } = req.nextUrl

    if (pathname == '/reset-token') {
        const index = searchParams.findIndex(x => x.key === "token")
        // You could also add token validation here.
        if (!index) {
          return NextResponse.redirect('/')
        }
    }
    return NextResponse.next()
}

Here is the repo which has some cool filtering of query parameters. This is a more soft approach instead of hard redirecting. https://github.com/vercel/examples/tree/main/edge-functions/query-params-filter

Nico also has a great answer on this, expect I wouldn't recommend using hooks like in his example, instead use isReady. https://stackoverflow.com/a/58182678/4918639

Comments

2

For NextJS version - 12.0.8

"If you export a function called getServerSideProps (Server-Side Rendering) from a page, Next.js will pre-render this page on each request using the data returned by getServerSideProps."

=async functions

refference:https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props#getserversideprops

Simply putting that async function on the page notifies NextJS of its presence.During prerendering stage of the component, the query object of the router will be empty.

isReady: boolean - Whether the router fields are updated client-side and ready for use. Should only be used inside of useEffect methods and not for conditionally rendering on the server.

refference: https://nextjs.org/docs/api-reference/next/router

solution:

import { useRouter } from 'next/router';

const Fn = () =>{

const router = useRouter();
const { param } = router.query;

const fetchData = async () => {
   await fetch();
} 

 useEffect(() => {
   fetchCat();
}, [router.isReady]);

}

1 Comment

I would consider checking that router.isReady is true before fetching
2

For Class Component Lovers

The even better approach is to listen for a dedicated event for this routeChangeComplete using this.props.router.events.on method, inside componentDidMount if you're using class component -

routeChangeComplete = () => {
    // this WILL have valid query data not empty {}
    console.log(this.props.router.query);
};
componentDidMount() {
    this.props.router.events.on("routeChangeComplete", this.routeChangeComplete);
}
componentWillUnmount() {
    this.props.router.events.off("routeChangeComplete", this.routeChangeComplete);
}

Ref: https://nextjs.org/docs/api-reference/next/router#routerevents

routeChangeComplete: Fires when a route changed completely.

Practically when isReady has become true or when router.query object has data.

5 Comments

I tried this. It does not work !
@Nima can you share any reproducible code?
I just copied your code but it logged empty ! ( I used withRouter for export)
Found the solution my self. I will post an answer.
@Nima did you find a solution in the end? This does not work for me Next.js 12, this.props.router.query is an empty object, if I wait until router is ready (router.isReady) it works
1

I resolved my problem that I need it in Hoc component. I wrapped using withRouter(withLocale(Comp)) and create conditional in HOC

export default function withLocale(WrappedPage) {
    const WithLocale = ({ router, ...props }) => {
        const { lang } = router.query;
        if (!lang || !isLocale(lang)) {
            return <Error statusCode={404} />;
        }
        return (
            <LocaleProvider lang={lang}>
                <WrappedPage {...props} />
            </LocaleProvider>
        );
    };
   return WithLocale;
}

Comments

1

in Next 13 router.query has been removed (due to the new app router) and replaced with useSearchParams(); hook instead, so you can use it like this:

import { useSearchParams } from "next/navigation";
const query = useSearchParams();

Comments

0

Next.js <= 10.0.5

This is a good work around, I found around from this comment

Add useQuery.ts helper file

// useQuery.js
import { useRouter } from 'next/router';

// Resolves query or returns null
export default function useQuery() {
  const router = useRouter();
  const ready = router.asPath !== router.route;
  if (!ready) return null;
  return router.query;
}

usage

// In your components (instead of useRouter)
const query = useQuery();

useEffect(() => {
  if (!query) {
    return;
  }
  console.log('my query exists!!', query);
}, [query]);

Comments

0

Class Component | 12/16/2022 | React JS 18.2.0 | Next JS 13.0.6

I got the answer for those who want to use Class Component. This was actually nowhere to be found ! Enjoy !

You will add if(this.props.router.isReady) and include return in the condition in render().

.
.
import { withRouter } from 'next/router';
import { Component } from 'react';

class User extends Component {

    ...
    
    render() { 
        if(this.props.router.isReady){ // Add this condition and include return ()

            // Do anything

            console.log(this.props.router.query) // Log 'query' on first render

            return (
                <div>
                    <SearchBar pid={this.props.router.query.pid} /> // Pass the query params to another component if needed
                </div>
            );
            
        }
    }
}

export default withRouter(User);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.