frontarm / navi

🧭 Declarative, asynchronous routing for React.
https://frontarm.com/navi/
MIT License
2.07k stars 71 forks source link

Best practices for passing subsets of data to routes #75

Open lostfictions opened 5 years ago

lostfictions commented 5 years ago

Hey! First of all, thanks for writing this library. I really appreciated how approachable the documentation is and how conversational it is in tone (particularly the "motivation" and "comparison" sections, two critical pieces of context I'm usually left wondering about when I find a new library in a established space like routing or SSR). And looking over existing issues, it seems like there's a pretty principled stance on keeping the API surface simple and the project scope contained. It's really nice to see a module that seems to have been approached with a lot of care.

Anyway, one thing I've been struggling with is how best to tackle passing the smallest amount of data possible to a given route. For example, if I have a set of blog posts, for a given post route I probably want to pass in the full text of the post, but I may want to also have previous/next links that show the titles of other posts, if they exist. And I probably also want to have an index page listing the title for each post, and maybe an excerpt of it (but not the full text).

In all of these cases, I want particular slices of data that might reside in a given file. react-static has a model for doing this that's super intuitive to me: you define routes somewhat similarly to Navi, but in a config file that's not bundled into a client-side build. That frees you to do work in advance -- synchronously or asynchronously import files, make API calls, munge data however you want, etc. -- and then pass only what you need to each route. I guess this is comparable to Gatsby, but to me it's radically simpler and much easier to reason about. (Unfortunately react-static has a ton of outstanding, showstopping bugs and there's no sign that they'll be addressed anytime soon, so I'm looking to migrate away.)

I did check how create-react-blog is doing this, but it really doesn't seem ideal: it looks like the problem is addressed by adding a layer of indirection that involves manually creating files with a subset of data and a thunk to grab the "real" data. To me this feels like a kludge that doesn't solve the general problem, and unfortunately it's not really acceptable for my specific use case where folks with limited technical skills will be creating and editing markdown files via prose.io or netlify-cms. It's way easier for all parties if all that metadata is colocated in the frontmatter and not reliant on code (and further to that we probably don't always want to, say, hand-author excerpts; some ahead-of-time work is probably necessary at some points).

Any ideas? I'm fairly sure webpack isn't smart enough to tree-shake dynamic imports yet -- and that's assuming it's even possible for something like mdx-loader to flag exports as pure if they're not contained in functions. Or maybe there's a way to write a kind of chained loader that can "pluck" a subset of exports... but that's kind of pie-in-the-sky, and I wouldn't want to have to implement or debug that for a use-case as simple as this (even if it strikes me as something that could be generally useful).

Sorry for the long issue!

jamesknelson commented 5 years ago

This is something I've struggled with too. One of my design goals with Navi is that it should be possible to do static rendering for a website built with create-react-app, or to enable server rendering by just replacing the react-scripts package with another package (this works; I just need to finish off the package and publish it). I don't want to require custom loaders, Webpack plugins or build tools; I want it to be plain JavaScript. Eventually, once ES6 modules and dynamic import() are supported on browsers, I'd like Navi to work without even using Webpack.

The problem with this, of course, is that if you store page metadata directly in routes, then it ends up getting built into the bundle, and you run into the problem that you've described.

I think the only clean solution to this is to have content and metadata stored externally from the routes, and to generate the routes as a function of the content/metadata. As to how to approach this, there are multiple options: you could take a Gatsby-like GraphQL approach, you could bundle page metadata as POJOs and generate routes from them at runtime as with create-react-blog, or you could avoid the problem entirely by using SSR and reading the page metadata from an API. But Navi is just a router, so I don't want it to make that decision for you.

What Navi does give you is a flexible way to generate a list of a site's URLs from any routing tree, and a composable way to declare that routing tree (as opposed to the monolithic routing trees in react-static, Next.js and Gatsby). This lets you build your own content-to-routes transform, lets you nest sites and layouts within each other, and makes it easy to create a static renderer that meets your requirements.

I think ideally, there'd be something like Next.js, react-static or Gatsby that provides its own build system, and uses Navi under the hood. It feels to me like Navi is a lot more suited to these kinds of projects than react-router or reach-router, but given their momentum, it's a hard sell to replace *-router with Navi in existing projects. If you wanted to create a Navi-based static generator though, I'd totally be onboard to help 😉

lostfictions commented 5 years ago

Thanks so much for the detailed reply! That all makes sense to me -- it for sure doesn't seem within the purview of Navi proper. I guess I'd been somehow hoping navi-scripts would have some allowance for it, but it's also fair enough that that doesn't try to complicate things too much.

I guess if I were to build a Navi-based generator I'd adopt react-static's approach of routes defined in a non-client config.js file and then allow the user to import the minimal route definitions via the mechanism of requiring a package rather than the config file -- except what the import returns would be composable into a broader app via Navi matchers, rather than something you can only plunk down wholesale.

I'd love to give it a shot, but sadly that sounds like a bit more than I have the wherewithal to take the lead on right now :( Beyond composability and getting a build system working, I'd probably also want to make sure it'd be easy to implement PRPL, since that's one of the killer things that Gatsby and react-static do. And best practices for easy configurability (of webpack, etc.) still seem to be an open question for many of these generators -- most of them seem to be plugin-driven, but often with no guarantees that plugins compose well or are simple to write, so dropping down often ends up being a pain point.

Maybe sometime later! Or if someone else ends up giving it a shot, I'd be happy to help out too.

jamesknelson commented 5 years ago

Yeah, I agree that it's a lot of work. That's a big part of why I've skipped it and just left navi-scripts as minimal as possible.

I think if we were to put something together like this, it'd be nice to just use create-react-app as the build system -- I personally feel that just ejecting is a cleaner option for configuration than complex, non-composable plugin systems. If CRA's SSR support PR is merged, this'd make navi-scripts a lot simpler as it wouldn't need to use jsdom under the hood, and would completely remove the need for a register() script. It'd definitely be worth revisiting this then.