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

Use with React Router 4 #1632

Closed
Knaackee opened this issue Apr 5, 2017 · 123 comments
Closed

Use with React Router 4 #1632

Knaackee opened this issue Apr 5, 2017 · 123 comments

Comments

@Knaackee
Copy link

Knaackee commented Apr 5, 2017

Is it possible to use next.js with the new react router v4?

@Knaackee Knaackee closed this as completed Apr 5, 2017
@oyeanuj
Copy link

oyeanuj commented Apr 18, 2017

@Knaackee Did you try out the possibility? I am wondering as well if someone has gotten it to work with any version of RR?

@sergiodxa
Copy link
Contributor

Next has his own router, why use RR?

@oyeanuj
Copy link

oyeanuj commented Apr 18, 2017

@sergiodxa Weighing options as I port my existing app to Next. I have a static, nested route config working and wondering what can I keep using, and what not..

@sergiodxa
Copy link
Contributor

@oyeanuj 🤔 you can gradually migrate to Next, just set Next.js to handle 1 route and use a proxy to have both your current app and the Next.js one, then move a second route from RR to Next.js, and that way you can keep your current app while migrating, eventually you will have everything in Next.js

@igl
Copy link

igl commented Jul 21, 2017

From looking at the examples it seems that next has a single route table like RRv2-3 and does not support "nested routes" like RRv4. These are nice.

I would try to use RR or is there a big caveat i don't know about?

@mrwillis
Copy link
Contributor

@igl Have you figured out a solution to that?

@jcheroske
Copy link
Contributor

The new react-router 4 paradigm of nested routes is a game-changer, and is something of a must-have in our current project. I'm wondering if anyone has succeeded in getting rr4 to work with next.js?

@malixsys
Copy link
Contributor

malixsys commented Nov 13, 2017

MemoryRouter does work, if not as intended...
Otherwise, dynamic components can be client-side only, where HashRouter works fine...

const AdminPage = dynamic(
  import('..../AdminPage'),
  { ssr: false }
);

@pegiadise
Copy link

I 'm also following @malixsys approach and handling client side routing with react-router and all the server rendered content with next router.

@dennis-b
Copy link

@malixsys @pegiadise can u please give some more details how to use next router and react-router together ?

@pegiadise
Copy link

You can set a flag in componentDidMount and conditionally render the <Router /> when the flag is truthy. (componentDidMount does not run on server 😉)

@jaydenseric
Copy link
Contributor

In fact, we're incorporating React Router inside Next.js soon :)
https://twitter.com/rauchg/status/948318111828099072

@JonCognioDigital
Copy link

Is this still happening? I can see the release notes for V6 canary and there is no mention of it.

@timneutkens
Copy link
Member

timneutkens commented Apr 2, 2018

Eventually. It's definitely on our minds. We've got other priorities right now (layout component, reliable development and a few other long standing open issues).

@JonCognioDigital
Copy link

That is a shame, it might be low priority for the team but it's basically the only thing stopping people like me from starting to use it. I read your tutorial until I got to the bit where it said that routes have to be set up twice then gave up.

@willopez
Copy link

@timneutkens adding react router integration would help increase nextjs adoption and make development better. Devs like myself would love to see this happen, please consider increasing the priority.

@romainquellec
Copy link
Contributor

romainquellec commented Apr 29, 2018

That is a shame, it might be low priority for the team but it's basically the only thing stopping people like me from starting to use it.

Same.

@benkingcode
Copy link

Can we at least re-open this issue so it can be tracked?

@timneutkens timneutkens reopened this Apr 29, 2018
@timneutkens
Copy link
Member

timneutkens commented Apr 29, 2018

I'm going to reopen this, but note that this is an open source project and we don't have a very strong case for it at this moment for zeit.co, since we use Next.js for everything.

This is why I was saying it's part of the longer term goals and can't be implemented immediately, but we're working towards it.

The features I've been introducing in Next 6 are actually working towards React Router support.

We had other priorities for Next 6, which are tremendously improving Next.js's reliability and scalability, for example 100x faster page resolving, App Component, making next export work in development, babel 7, etc.

I hope this elaborates on my earlier comment.

So the TLDR is:

  • We're going to do it, but not immediately
  • Next 6 has many improvements to different parts of Next.js

@rauchg
Copy link
Member

rauchg commented Apr 30, 2018

To further extend @timneutkens's comment: we definitely want RR, but we don't have any pressing limitations in the current Router that make it a priority. All the routing use-cases you can imagine have been implemented successfully with the current Next.js API

There are two reasons we really want React Router support for:

  • Simplify our own Next.js codebase, not have to maintain our own router, make things smaller and modular
  • Simplify the migration path of large applications already using RR

As such, I agree we should keep this issue open!

@merrywhether
Copy link

As a counterpoint, not having react-router means that next works nicely with relay-modern and was one of the reasons we switched to next from our old react-router app.

@igl
Copy link

igl commented May 4, 2018

@merrywhether I worked on an RRv4 app with relay-modern last year. Care to be more specific?
I don't remember us having serious issues because of either.

@merrywhether
Copy link

@igl This is according to relay's documentation: https://facebook.github.io/relay/docs/en/routing.html#react-router-https-reacttrainingcom-react-router

The problem is that RRv4's component approach doesn't allow for determinism during the pre-compilation step, which can result in request waterfalls in the client.

@benkingcode
Copy link

@rauchg Out of interest, my understanding of Next's router is that it doesn't support the concept of nested routing, so you can keep outer markup while navigating within a page. Do you know if there is a way to make this possible with the current router?

@sergiodxa
Copy link
Contributor

@dbbk check our nextgram example app (https://github.com/now-examples/nextgram), it does exactly that

@merrywhether
Copy link

In next 5, we're accomplishing "outer markup" by having top-level layout components that all of our pages extend: base layout has top nav, then a few sub-layouts that extend base for lists/details/etc, and then our pages components extend these sub-layouts with their own specific content. In next 6, you could also accomplish basic "outer markup" using _app.js I believe.

@merrywhether
Copy link

merrywhether commented May 3, 2020

@lishine I'm sorry for a bit off-topic here, but in most case i don't see Router is the main issue here. Next.js router is good, declarative, convention over configuration is good. The only thing that i can't use in Next.js is the data fetching methods like getInitialProps,...
In my apps, each component will need to declare its own data right in the same place, in the same file, whatever it's graphql or rest.
Top pages component is just for composing child components, and fetching data is not its job. Child component must get data of itself, not its parents.

Here's my sample of code that i'm using for my app for clarity:

...

As you see, you have one component with its own data. You can put it anywhere you want.

Of course, you can use Next.js pattern without any problem, but in my case, i prefer isolation for data and component as much as possible for easier maintainance and refactoring later.

@revskill10 Why can't you have the child declare it's data fragment and then have the parent include that fragment in the top-level query? Especially as you create more child-associated fragments, you have perfect data isolation. Having a parent query with a child fragment is no different than having that child declared in JSX, so you have the same level of coupling but avoid request waterfalls (much harder in REST, sadly).

We have a Relay-Next app and this pattern works perfectly, with data-encapsulation and reusable composition, and we leverage getInitialProps with ease.

@revskill10
Copy link
Contributor

@merrywhether Not to mention about the much harder in REST, your approach has a problem that, you can't decide which fragments will be SSG/SSR or will be CSR.
In my approach, it's as easy just as importing that component with { noSSR: true/false} .

@dantman
Copy link

dantman commented May 24, 2020

I find the lock-in to a vendor specific routing library that does not share an underlying implementation with any major routing library to be extremely concerning.

I recently did a review of Next.js to evaluate whether it should be used as the base for the common core shared between a number of frontend applications being built for our current client. While Next.js has potential; this router is one of the major reasons I ended up rejecting Next.js and instead keeping CRA as the base for these applications.

I do understand the difficulty of using the component based top-level API of react-router/@reach/router as the base for SSR. But that isn't a good reason for the underlying implementation being entirely custom. Gatsby's SSG has the same concerns as Next.js and a semi-similar file based routing structure; but to my understanding Gatsby uses @reach/router under the hood to power its routing implementation instead of reinventing the wheel or exposing a Link API that is incongruous with the Link API used by other libraries.

@laurencefass
Copy link

@dantman may i ask what did you choose for Next.js alternative. Assuming you needed server side rendering.... I have been trying out After.js maybe it can provide some inspiration/ideas how to implement in Next.js if not being supported by zeit.

@dantman
Copy link

dantman commented May 24, 2020

@dantman may i ask what did you choose for Next.js alternative. Assuming you needed server side rendering.... I have been trying out After.js maybe it can provide some inspiration/ideas how to implement in Next.js if not being supported by zeit.

Sorry, I don't have a helpful answer for you. SSR wasn't a hard requirement so we just kept using CRA, which the prototype was built with.

I thought Next.js had promise as a universal framework since it recently gained the ability to have a mix of SSR/SSG/client-only pages and could run as an isomorphic app or as a static/PWA app. The WebPack customization was tempting because CRA has been making using globalize-compiler hard. The Next.js server was a neutral/positive because we needed an API server anyways for a GraphQL/REST bridge. And the option of SSR/SSG was a positive since I'm building the core a half dozen applications will be based on and it's not impossible it could end up useful in the future. However I also had some issues with the way Next.js' SSR works and these positives were not worth the trouble caused by the router.

@rauchg
Copy link
Member

rauchg commented May 24, 2020

@dantman

I find the lock-in to a vendor specific routing library that does not share an underlying implementation with any major routing library to be extremely concerning.

It’s quite strange to qualify an open source component with an API that hasn’t changed in 3 years due to its great stability and “product/market fit” as “lock-in”

Next.js has succeeded and continues to show the growth that it does because of its router and not in spite of it.

As many have seen me comment on Twitter, once upon a time we seriously entertained merging in some router (although I’m confused as to which one is the standard in your mind, Reach or React Router, and at what major version). But the world pulled us in other interesting directions. In reality, we didn’t even know what the point was of adding it, because everyone kept succeeding with Next.js and building wonderful products.

When I indeed would inquire to people why they wanted a different router API, the reason that most frequently would come up is because people were stuck and frustrated with home grown framework solutions built on a router, and they couldn’t wait to migrate to Next. It wasn’t a reason rooted in product design.

When I say that Next succeeded because of it router, it’s because it eliminated two problems:
1️⃣ The idea of having to choose a router. Us removing this is in retrospect an excellent decision. In all the time Next.js with its stable router has existed, the router world has split up and launched all kinds of API changes

2️⃣ The learning curve of a router. Many workshops and tutorials have been given about routing, but the Next.js filesystem-first routing takes 2 seconds of explain and gives you the platform to build incredible things, and you move right into product development

I want to stress this last point. Consider one of the newest and fastest growing websites in the world, TikTok.com, built on Next.js. The entire routing topology of that website can be explained with that two second learning curve. https://www.tiktok.com/@sheezus.christ/video/6824007862197439750 is pages/[user]/video/[id].js and https://www.tiktok.com/trending is pages/trending.js

Many of the recent Next.js innovations you mention you like, like hybrid static/SSG/SSR, are also enabled by our router design. In fact, the router will also enable many other similar optimizations that are coming in the future to deliver less JS to clients.

However I also had some issues with the way Next.js' SSR works and these positives were not worth the trouble caused by the router.

Would love to hear about these. The example above is powered by Next.js hybrid static/SSR and we see lots of success with it across the board!

@rauchg rauchg closed this as completed May 24, 2020
@merrywhether
Copy link

merrywhether commented May 24, 2020

This is such a funny thread. Next has concrete reasons for avoiding waterfall routing (aka react-router and friends) as getInitialProps enables a lot of performance optimizations, and Next's approach is turning out to be quite popular, especially as some people specifically want those optimizations. Performance comes with design tradeoffs, and sometimes you may prefer to choose design over performance, but that doesn't make the tool wrong, just wrong for you for a specific project.

Ultimately, react-router isn't the be-all-end-all of routing solutions. It has its pros and cons, as does Next. FWIW, Facebook doesn't use react-router, and they probably know a thing or two about using React. So it's fine to have alternatives, and actually one of the great things about the JS ecosystem: let different things compete in the arena of ideas and ultimately we all move forward.

@rauchg
Copy link
Member

rauchg commented May 24, 2020

Since I’m closing the issue, I want to make it clear that we are always open to comments, feature requests and bug reports on the routing capabilities.

Ideally, those would be product driven (“I need to do X but it’s not possible” or “Y is possible but not ergonomic”). We are always on the lookout for improvements that help you make lean websites and applications with great user experiences 🤗

@avin-kavish
Copy link

@rauchg Can you explain the reason behind having two props, href and as to a link? Why can't it infer the intended page based on the value of the as prop?

For example in express if I have a route as /blog/:slug, i can just send a http request to /blog/hello-world and expect the router to route to it. I don't have to send both /blog/:slug and blog/hello-world to the server.

@rauchg
Copy link
Member

rauchg commented May 24, 2020

@avin-kavish that’s a great question. There’s an important distinction to be made between what the URL displays and what page is to be loaded. As a matter of fact TikTok uses that to render certain things in modals, that when you refresh the page become other pages. However, one big area of improvement here is that we should perhaps statically enforce that you’re referencing a valid page that exists in the file-system. We will follow up by creating a Discussion on this and tag you!

@martpie
Copy link
Contributor

martpie commented May 24, 2020

I think an issue already exists for that #8207

@donaldpipowitch
Copy link

In case someone watched this issue for a react-router like "nested routes" feature which allows navigating to new pages without re-rendering everything like I was, there is actually a dedicated issue you can watch and vote for. I just found out about that one: #8193

@dantman
Copy link

dantman commented May 24, 2020

@rauchg

To be clear in this my primary issue is not the lack of a RR/reach style component API, I am fine with a SSR capable file/config based API. Although I am a bit optimistic that in the future Suspense could make alternate SSR/routing APIs viable. My primary issue is the router being completely custom with any common concerns in the implementation being re-implemented instead of shared with any part of the wider React community.

I find Gatsby's approach to be acceptable. They have a SSG capable file+config based routing API and export their own enhanced Link component; but they use @reach/router to power the underlying implementation.

I find the lock-in to a vendor specific routing library that does not share an underlying implementation with any major routing library to be extremely concerning.

It’s quite strange to qualify an open source component with an API that hasn’t changed in 3 years due to its great stability and “product/market fit” as “lock-in”

The router is intrinsically tied to Next.js. Adopting Next.js for one reason means being tied to the router. If we adopt Next.js and later discover that next/router has a limitation that turns out to be crippling for something we're trying to do there is absolutely no reasonable escape hatch. "Lock-in" is a perfectly fine descriptor for that.

Lock-in alone wouldn't be a major issue. Gatsby's use of @reach/router would also be lock-in. But @reach/router is used outside of just Gatsby. I don't have to trust the Gatsby community alone to maintain the entire router stack; I can trust that a larger community relies on it and there are more stakeholders involved (specific to the routing stack) to ensure it's maintained, keeps up with difficult routing issues (e.g. accessibility), and is depended on by a wider more varied community that will likely share any potentially crippling issues we could encounter in the future.

although I’m confused as to which one is the standard in your mind, Reach or React Router, and at what major version

In terms of the de-facto standard of what <Link> should look like. Both Reach and React Router (RR) share very similar APIs. e.g. <Link to='/about'>About</Link> is valid in both RR and Reach (and of course by extension Gatsby). Which makes Next.js' idiosyncratic <Link href='/about'><a>About</a></Link> so much more jarring.

In terms of what I think Next.js should use. I'm not tied to a specific library, if Next.js were already using one I wouldn't suggest switching to another. My major concern is that the routing implementation be shared with a library with a wider community outside of Next.js.

In practice though that's relatively moot. As so far I haven't seen any React router besides RR and Reach with a large userbase. And RR and Reach are going to become one library, so whichever you start with the end result will be the same.

@laurencefass
Copy link

laurencefass commented May 27, 2020

I tried Next a while back but lack of flexibility on the router led me to discover a Razzle implementation called After.js https://github.com/jaredpalmer/after.js?utm_source=hashnode.com.

As React Router is just a package can we not import it and limit rendering to client side only so it works like an SPA where its needed and give us nested component routes? Isnt React router just a set of components to be (potentially) embedded in pages and loaded with the Next.js page router like any other components?

I read in earlier threads that zeit plans to integrate RR is that still true today?

@avin-kavish
Copy link

Why don't we allow nested routes in next.js router as a last resort and make it clear that these areas will not be pre-rendered. At the very least it will save us the effort of having to avoid writing the if conditions that we have to inevitably write when we want a sub-resource in the page to change based on the route.

@cloud-walker
Copy link

I'm adding my vote on this issue.

Another pro, not mentioned, is that RR its more testable, (AFAIK there is no official nextjs API for router testing), it has MemoryRouter for wrapping tests and stories.

@dantman
Copy link

dantman commented Jun 23, 2020

Next.js has a lot of good features (automatic WebPack, static files, and TypeScript like CRA but for more than just PWAs; API routes; serverless support, Fast Refresh, and even experimental Expo support for web+native apps) and a core for SSR and SSG even if the API for it isn't great. But while the built-in routing system and SSR/SSG works for some; for others they hobble development because the limits of both APIs offer the wrong trade-offs for said project.

How about a compromise. Next.js already has plugins. Instead of replacing the router internally what if we separated the router and SSR API (i.e. the getStaticProps/getServerSideProps in route files) from the core of Next.js. e.g. We could put the very fundamental core parts in @next/core and move the current opinionated router and get*Props APIs to @next/router. For simplicity and backwards compatibility next could be a framework that re-exports @next/core and comes pre-configured with Vercel's preferred router. But then it would be possible for the community develop routers and SSR/SSG APIs with different trade-offs that are better suited to projects that would otherwise be stuck throwing out the good parts of Next.js with the bathwater.

Some thoughts on what the Next.js core would require the router plugin to provide:

  • Given a request { url, body, etc } the core would expect the router plugin to render this request to a document (string or streaming) or throw a response (i.e. 404 or redirect).
  • On export the core would expect the router plugin to provide a list of pages that need to be rendered.

@next/router would probably implement the same patterns via the api by doing things like:

  • Given a request, identifying the route file responsible and rendering as normal
  • On the client doing something similar to whatever it currently does to know when to call the SSR API and render the route it needs (possibly moving the API based SSR API to a normal looking API route)
  • On export using the pages file tree and getStaticPaths to provide the list of static pages

I could probably see myself experimenting with a router plugin using React Router v6 with @apollo/react-ssr and Suspense with react-ssr-prepass or react@experimental. Which forgoes SSR-only routes for isomorphic SSR routes and implements SSR/SSG without a restricting get*Props style API.

@avin-kavish
Copy link

avin-kavish commented Jul 7, 2020

What I realised is, nested routing + SSG is achievable without breaking the current API. So we have getStaticPaths at the moment, we can use that to hint at nested routes for pre-rendering. For example,

Given a route /foo/[...slug],

function FooPage() {

  return (
      <Switch>
          <Route path="/foo/bar">
              <SomeResource />
              <Route path={`${parenthPath}/baz`} component={SomeSubResource} />
          </Route>
          .... more routes
      </Switch>
  )
}

can be pre-rendered with,

export async function getStaticPaths() {

  return {
    paths: [
      { slug: [ 'bar' ] },
      { slug: [ 'bar', 'baz' ] },
    ],
  }
}

With the way it is at the moment next.js is like a server side framework with the convenience of creating pages in react. It doesn't feel as powerful as react-router. Directory based routing may have it's place in building consumer facing sites like tiktok as mentioned before, but for complex data driven application portals, nested routing is still king. It is why single page applications were created in the first place, it allows for changing bits of the UI without having to replace the entire page. This can be leveraged to model resource-subresource relationships quite conveniently. As it stands, I may use next.js if I were to build the public pages of a consumer site like an e-commerce site but when I need to build the private areas such as admin, buyer & seller portals I would switch to CRA.

@martpie
Copy link
Contributor

martpie commented Jul 7, 2020

@avin-kavish the main issue is not to make it work, but to make it optimized: each page on Next.js default have their own bundle and are optimized for speed.

If you start adding a lot of content/sub-content in a single page like you did, you could end up with a quite big bundle in the end (which is not "terrible" per se, but you should just be aware of the trade-offs). You might be able to do some manual optimization with next/dynamic thought :)

@mporkola
Copy link

@rauchg the only dimension isn't whether next router is good or bad. Another very important thing is migration to and from next.js and community support, and #1632 (comment) put it well. Next.js is such a good solution for abstracting away a lot of the boilerplate a high-quality SSR app needs, and as such it's a very inviting migration target for many web apps. The problem right now is that it would need a complete routing rewrite both for migrating into next.js, and out of it if the need comes.

Pluggable routing suggested by @dantman earlier would solve this issue in a very elegant matter, and wouldn't require anyone to sell their principles 😉

@merrywhether
Copy link

The problem with react-router (and any nested routing solution) is that it makes static analysis much harder because the relevant code paths for any specific URL are not available without running (or simulating) the code itself. Next is more than just a "put UI on a webpage" framework, which is why for instance they worked with the Chrome team on creating more highly optimized bundling strategies.

react-router users are used to using react-loadable directly since it rr delegates that responsibility entirely to end-users, but Next tries to abstract and automate this which isn't easy. The proposed pluggable router approach would probably have to involve router plugins providing extensive build-time information to the framework in order to achieve the same type of output, since every router would have to know how to generate such information based on its own patterns.

Purely speculation, but I imagine a lot of things are in limbo while React finishes up Suspense, since making that a first class pattern in the library will greatly affect all of the router libraries and also give a concrete foundation upon which to build async/loadable bundle patterns.

@dantman
Copy link

dantman commented Aug 30, 2020

Long story short / a good summary of below: There is no such thing as a "one-size fits all solution". Everything has trade-offs. Every "advantage" of the way Next.js currently does things comes with a disadvantage. What advantages/disadvantages are are most important is something that is different for each project. Hence, the recommendation for a pluggable router/ssg/ssr architecture. Different projects need different things and right now Next.js only works for the projects whose priority on trade-offs aligns with the way things are hardcoded in Next.js.

The problem with react-router (and any nested routing solution) is that it makes static analysis much harder because the relevant code paths for any specific URL are not available without running (or simulating) the code itself.

Honestly that only matters if you are using SSG and have a bunch of non-dynamic routes. If all your routes are SSG or client-only, then that's not useful. And if most of your routes are dynamic then you have to explicitly declare them using getStaticPaths anyways. Which would be the same amount of work as explicitly defining the routes in a hypothetical Next.js plugin that just uses raw react-router without modification and asks you to explicitly define static routes.

It is an advantage sure, and some teams will want that. However that is only one sub-group of potential Next.js users. There are other groups who may want some of the advantages that RR or another routing library provides and the need to explicitly declare static routes is an acceptable tradeoff or a non-issue. For example, my last few projects have been the kind of B2B/B2C apps where 100% of things are behind a login page and there's no point statically rendering any of it. Next.js has some advantages over CRA that would have made Next.js preferable; But things like the router were big red flags and we just kept using CRA. Those projects would have been very well suited to a Next.js with raw react-router.

This also assumes that everyone who doesn't want next/router wants to use the JSX form of react-router. That is not the only type of next/router alternative.

One other type of router would be the Gatsby.js style. Where a project still uses a filesystem based pages structure, but the internals are implemented with another routing library like react-router (Gatsby uses @reach/router internally). This kind of router plugin would give you the same static-analysis advantages. But it would also give you whatever advantages the alternate router has not related to the nested routing. e.g. Accommodating potential preferences for route API, better handling of accessibility, better integration into the ecosystem around that router.

And of course even within the react-router ecosystem the JSX form is not the only way to use react-router. There is also react-router-config. Which is easy to do static analysis on and also supports nested routing.

react-router users are used to using react-loadable directly since it rr delegates that responsibility entirely to end-users, but Next tries to abstract and automate this which isn't easy.

And some of us are fine with handling code-splitting ourselves. Nested routing can be more important to a project than automatic code-splitting. It's all about which trade offs are best suited to a project.

This is more of a side note, but I'm actually curious about the potential for a babel/webpack plugin that would do this automatically for routes. Which is something that would be useful outside of just the Next.js ecosystem.

Also react-loadable is an effectively defunct library (2 years since publish, not accepting bug reports). Personally I would rather manually do code splitting with @loadable/components than use something automatic built-in to Next.js based on an internal fork of react-loadable.

The proposed pluggable router approach would probably have to involve router plugins providing extensive build-time information to the framework in order to achieve the same type of output, since every router would have to know how to generate such information based on its own patterns.

Yup. That's generally how a plugin system would work. Honestly the fact that this kind of information can be provided by the plugin is an advantage, not a problem. Having this in a plugin means that should the way Next.js gathers this information not be suitable for some types of projects' needs, it can be replaced with one that does fit the needs of those projects. But without needing to fork and rewrite all of Next.js to do so.

Purely speculation, but I imagine a lot of things are in limbo while React finishes up Suspense, since making that a first class pattern in the library will greatly affect all of the router libraries and also give a concrete foundation upon which to build async/loadable bundle patterns.

This itself could actually a pretty good argument to work on a pluggable router system. Right now because all the routing and SSR stuff is hardcoded into Next.js it is not possible to easily do the experimentation into any type of future routing system within Next.js. How does Suspense affect Next.js' routing and other routing libraries? Not something you can experiment with (at least in regards to Next.js' SSR and bundling) without forking and rewriting chunks of Next.js.

Router plugins would be a great place to do this kind of experimentation. As long as the plugin API is low-level enough it would be possible to fork just the next/router plugin and try writing a react@experimental + Suspense based version of that router. And because this is a plugin (not a fork of all of Next.js), it would be easy to opt-in to the experiment and test out the new Suspense based router. This is important now rather than later because this kind of experimentation is why react@experimental exists, to gather feedback from real projects.

@merrywhether
Copy link

merrywhether commented Aug 31, 2020

@dantman I'm not disagreeing with anything you've said. It is all about trade-offs. The biggest tradeoff being what the Next team spends their time on. So far, low-config high-performance SSR seems to have been their main focus. I do understand that this isn't necessarily relevant for all users, but it is the angle Next initially used to stand out (which is why we choose them at work). They've recently dug more into SSG, seemingly due to the popularity of Gatsby and Jamstack, but they're still best for SSR imo.

Honestly that only matters if you are using SSG and have a bunch of non-dynamic routes

I'm not sure what you mean by this, as SSG vs SSR doesn't really matter for trying to deliver the smallest possible JS payload for the first page ("critical path" JS), nor do dynamic routes. Minimizing critical path assets is generally pretty important for SSR apps (thus all the effort) so this is appreciated. And honestly, if all you're making are login-walled CSR-only apps, then Next does have a lot of downsides compared to CRA (SSR will never be as convenient as CSR). It sounds like you're discounting those apps that are actually doing runtime SSR (with server-side handling of login/personalization) specifically for perf wins. Not everything fits into the SSG vs CSR dichotomy.

some of us are fine with handling code-splitting ourselves

Some of us are are quite capable handling webpack, react-dom/server, etc ourselves too. Next's goal so far has seemed to be to make such ejection rarer and rarer just like CRA. And you're right, I should have said react-loadable-alike, because there are lots of solutions in that space, and they are only getting more exotic with the emerging data-plus-code patterns from libraries like Relay.

Ultimately, I don't disagree that a pluggable router system might be nice. I was just pointing out that it would be a lot of work for the Next team to remove things that are central tenets of the framework (like bundle-splitting logic) and extracting them into a pluggable architecture. And my speculation was highlighting the fact that I wouldn't want to start designing a foundational change to my library that could easily be upended by upcoming changes to my core dependency. I certainly agree that some aspects of Next's design are limiting, but for the most part those limits make sense given their design constraints thus far.

@dantman
Copy link

dantman commented Aug 31, 2020

Honestly that only matters if you are using SSG and have a bunch of non-dynamic routes

I'm not sure what you mean by this, as SSG vs SSR doesn't really matter for trying to deliver the smallest possible JS payload for the first page ("critical path" JS), nor do dynamic routes.

We're probably thinking of different reasons why the ability to statically analyze what routes exist is necessary. Assuming we're both talking about "static analysis" as "the ability to identify the list of routes (e.g. ['/about', '/', '/profile']) at build time simply by reading the code".

It sounds like you're talking about some type of JS bundle optimization? Which I haven't found any information on in the documentation, so I'm not aware of exactly what type of optimization based on static route analysis you're thinking of.

My thought was that this static analysis of routes was primarily useful for SSG. i.e. Because the pages/about.js file exists when you build your site Next.js knows that an /about route exists and it needs to pre-render the html for this route, even though you never explicitly told it there's an /about route to pre-render.

SSR doesn't need pre-built html since it only does that when a request comes in (at which point it does run the code and has one path to render). Client-rendered pages don't have pre-rendered html at all. And if your SSG page is dynamic then you need to declare all the paths anyways. Hence my thoughts.

And honestly, if all you're making are login-walled CSR-only apps, then Next does have a lot of downsides compared to CRA (SSR will never be as convenient as CSR).

What downsides are you thinking of in Next.js in regards to CSR apps? Aside from the aforementioned router issues.

To my understanding Next.js supports the full gamut of SSR/SSG/CSR routes. So it is supposedly still suitable for writing login-walled CSR-only apps.

Personally my perspective is definitely from someone writing a lot of largely CSR apps, with occasional SSR and SSG needs, wanting to have a single robust toolkit for all my projects, no mater what mix of SSR/SSG/CSR they need.

From my experience CRA has a fair number of disadvantages even within CSR-only apps that could make Next.js' CSR-pages advantageous. WebPack config customization without ejecting is a big one. This caused me a lot of pain when I couldn't simply use the globalize-compiler WebPack plugin when I was adding i18n to an app. The ability to opt-in to SSR/SSG for specific pages even if most of the pages are CSR is also an advantage (e.g. 99.9% of your pages are CSR and behind a login page; but you have landing page and maybe terms/contact pages you want SSG or SSR on). Can't do any of those things reasonably with stuff like CRA.

some of us are fine with handling code-splitting ourselves

Some of us are are quite capable handling webpack, react-dom/server, etc ourselves too. Next's goal so far has seemed to be to make such ejection rarer and rarer just like CRA

Honestly, manually doing route based code-splitting (making sure you modify one to use your route components via React.lazy or an alternative library instead of a direct import) is a far far far ways away from manually managing a custom WebPack config or writing your own SSR handlers with react-dom/server.

It's entirely reasonable to not want to manually write a whole WebPack config or a custom SSR server (i.e. want to use a widely used framework like Next.js), but still be ok with using react-router and manually doing the route based code-splitting. Especially if opting in to automatic route base code-splitting means loosing the widely used router library you're using and using a router missing a number of features you may need with an API very different than any of the routers in wider usage.

@eddyw
Copy link

eddyw commented Oct 22, 2020

I always land in this issue when searching for a way to integrate react-router with NextJS that doesn't require to create a custom server, so I decided to give it a try myself.

With react-router v6 (beta), create a custom _app:

// _app.js || _app.tsx
import * as React from 'react'
import App from 'next/app'
import NextRouter from 'next/router'

export default class CustomApp extends App {
	render() {
		const { Component, pageProps } = this.props

		if (process.browser) {
			const { Router } = require('react-router-dom')
			const { createMemoryHistory } = require('history')
			const history = createMemoryHistory({
				initialEntries: [this.props.router.asPath],
			})

			history.listen(function ({ action, location }) {
				const url = {
					hash: location.hash,
					pathname: location.pathname,
					search: location.search,
				}
				switch (action) {
					case 'PUSH':
						return NextRouter.push(url)
					case 'REPLACE':
						return NextRouter.replace(url)
					default:
						return void 0
				}
			})

			return (
				<Router location={history.location} navigator={history} action={history.action}>
					<Component {...pageProps} />
				</Router>
			)
		} else {
			const { StaticRouter } = require('react-router-dom/server')
			return (
				<StaticRouter location={this.props.router.asPath}>
					<Component {...pageProps} />
				</StaticRouter>
			)
		}
	}
}

Why

It isn't easy to manage optional catch all routes in NextJS (e.g: /foo/[[...bar]].js), so I'm exploring a way to be able to use react-router for this kind of pages. Maybe others have different reasons but that's my main concern and react-router provides a nice API, specially in v6 which is currently in beta.

How it works

It just creates a custom MemoryRouter instead of a BrowserRouter so we don't mess up browser history by having NextJS router & NextJS router. It listens to the memory history changes for PUSH and REPLACE so you can use react-router hooks or Link to navigate but under the hood, it'd be calling NextJS router methods .push and .replace.

Calling NextJS router methods is needed, otherwise route changes won't actually trigger NextJS get*Props methods. In other words, it'd work similarly as shallow option using NextJS Link. The downside of using react-router's Link is that there is no prefetch. However, you can still use NextJS Link instead and react-router can still react to route changes.

The cool thing about it is that you can now leverage NextJS dynamic and react-router routes and do things such as:

// /foo/[[...bar]].js
import * as React from 'react'
import { Route, Routes } from 'react-router-dom'
import dynamic from 'next/dynamic'

const Home = dynamic(() => import('src/views/Home'))
const About = dynamic(() => import('src/views/About'))
const Navigation = dynamic(() => import('src/views/Navigation'))

export default function Root() {
	return (
		<>
			<Navigation />
			<Routes>
				<Route path="/foo/" element={<Home />} />
				<Route path="/foo/about" element={<About />} />
			</Routes>
		</>
	)
}

Anyways, I hope this helps someone. I haven't used this in production and this code is from a local playground I have, so probably there are things that could be improved but it's a start.

@colinhacks
Copy link
Contributor

colinhacks commented Nov 5, 2020

Using React Router with Next.js 9.5+

If you're using Next.js 9.5 or later the correct way to do this is with Rewrites. Do not use a custom server! There is a detailed tutorial on how to do this here: https://colinhacks.com/essays/building-a-spa-with-nextjs

The basic idea:

  1. Create a custom App (/pages/_app.tsx)

  2. Return null if typeof window === "undefined". This is required to prevent react-router from throwing errors during the SSR step!

// pages/_app.tsx

import { AppProps } from 'next/app';

function App({ Component, pageProps }: AppProps) {
  return (
    <div suppressHydrationWarning>
      {typeof window === 'undefined' ? null : <Component {...pageProps} />}
    </div>
  );
}

export default App;

The suppressHydrationWarning attribute is to prevent warnings that React throws when the server-rendered content disagrees with the client-rendered content.

  1. Rewrite all routes to the homepage
// next.config.js

module.exports = {
  async rewrites() {
    return [
      // Rewrite everything else to use `pages/index`
      {
        source: '/:path*',
        destination: '/',
      },
    ];
  },
};

Then you can use React Router like normal! There is a lot more context/explanation in the linked tutorial but this will get you started. https://vriad.com/essays/building-a-spa-with-nextjs

@iqbal125
Copy link

@colinhacks Nice solution can confirm it works. Something to think about maybe is moving the app to its own page like app.js or routes.js or something. Then having the rewrites to

// next.config.js

module.exports = {
  async rewrites() {
    return [
      {
        source: '/app/:path*',
        destination: '/app',
      },
    ];
  },
};

Just something to think about, your solution is the best I found.

@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

No branches or pull requests