Contextual routing is a widespread UI technic made popular by applications like Facebook, Instagram and Reddit usually in the shape of Modal Routes.
Modal routes pattern consists of opening a modal while temporary replacing the current URL (usually with the one pointing to the resource being displayed in-modal):
Basic contextual routing
Next.js router recently added support for contextual routing by simply providing Link
component with the relevant href
+ as
props.
import Link from 'next/link';
<Link
href="/post-list?postId=42"
as="/post/42"
>
<a>Open modal route</a>
</Link>
The above example would update browser address' path to /post/42
while rendering the page specified as href
(/post-list
with postId
parameters equal to 42
).
This leads to 2 possible outcomes:
- user closes the modal and the URL is restored to the value where contextual routing was started (in our case
/post-list
) - user refreshes the page landing on the actual page described by the URL
HREF property
The minimal necessary information to render a Next.js page consists of:
- page
pathname
(eg./post/[id]
) - page params (eg.
id=42
)
The main idea behind contextual routing consists of creating a divergence between the URL displayed and the page actually rendered.
Since contextual navigation replaces the URL, it means that rendered pages cannot rely anymore on the URL to retrieve pathname
and relative page params.
href
prop plays therefore the crucial role of supplying the aforementioned information as a single string:
// page pathname + all required params as query string
const href = router.pathname + `?param1=1¶m2=2`
Real world scenario
Before starting contextual routing navigation you need to know 3 information beforehand:
-
as
path (the path displayed during contextual routing) -
href
path - return
href
(the path to return to to end contextual routing)
href and return href are trivial to get when the starting page has a static path, let's say: /post-list
.
Things gets trickier when starting page path is not statically defined, in other words when the path includes dynamic parameters like: /post-list/bob
(where bob
is an author name).
This means href
has to be generated from initial page pathname
plus route params and then persisted during the whole contextual routing navigation in order to keep the page alive.
On top of this a developer might want to extend available route params with extra ones to be made available during contextual navigation. Eg:
const as = 'post/42'
const returnHref = 'post-list/bob'
const href = 'post-list/[author]?author=bob&id=42'
The example above provides id=42
as extra param to make the modal route aware of which post should be displayed.
This demo shows a basic implementation of what you just read.
The devil is in the details
There's is an extra pitfall worth mentioning: the application would loose reference to returnHref
after a page reload followed by one or more back button presses.
This means the application won't be able to restore the initial URL while the modal route is open, breaking the user flow or forcing Next.js to reload the page.
One hook to rule them all
In order to overcome this issue and make contextual routing setup trivial, I wrapped the necessary boilerplate logic in a React hook published as next-use-contextual-routing
.
You can read the source code and star it on github. It's fully tested and weights ~0.5 kb gzipped.
import Link from 'next/link';
import { useContextualRouting } from 'next-use-contextual-routing';
//...
const { makeContextualHref, returnHref } = useContextualRouting();
<Link
as="/post/42"
href={makeContextualHref({ id: 42 })}
shallow
>
<a>Open modal route</a>
</Link>
The hook returns 2 values:
makeContextualHref
: a function returning the href
value necessary to start contextual navigation. It optionally accepts an object providing extra href
parameters.
returnHref
: the path to return to to close contextual navigation.
The demo mentioned above makes use of next-use-contextual-routing
hook. Check it out to make sure it can suit your needs.
Top comments (6)
Hey there, I featured this article in the latest Next.js News: twitter.com/vvoyer/status/13182876... (at the bottom of the newsletter content).
BTW, are you sure it's ~500 kb gzipped? Seems like a lot :)
Hi Vincent, it's definitely a lot! I've just refactored the article's dev dependencies and the weight got down to 0.5kb :) Thank you
Hi Andrea, I am coming here again after getting a peer dependency error of the package during my deployment. The last published package is configured to use next version <= 12. I saw the repo and the version was updated to 13 there. Looks like the package needs to republished with latest changes? I am still waiting for the latest version that supports next 13. I really hope you see this. Been trying to reach you via twitter and contact from your website. Thanks.
Thank you so so much for this article and that precious hook. I was able to get the things done for home route i.e. '/', but couldn't get it work for dynamic routes and spent hours googling until I reached here. Tried the hook and now it is working perfectly. Thanks once again and please keep the hook alive as long as you can!!
This is almost what I needed. But I what really want is - when you do a hard reload, the page looks exactly the same - with the modal open etc.
Wow awesome work, thank you!