Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork1.4k
Monorepo example#755
-
Hey guys, we appreciate you sharing this great library. So far, everything is amazing, but we are having issues strongly type using a monorepo. Before we say which monorepo to avoid promoting one over the other; do you have any examples how tanstack router is meant to work for Typescript in a monorepo? Specifically, we have a monorepo package called "router", where tanstack router is installed, and the actual "router" file in an app of the monorepo where all the routes are configured and the router exported for the app. Thank you kindly. |
BetaWas this translation helpful?Give feedback.
All reactions
Replies: 6 comments 13 replies
-
With Nx Workspace, I updated the Vite Config as follows: /// <reference types='vitest' />import{defineConfig}from'vite';importreactfrom'@vitejs/plugin-react';import{nxViteTsPaths}from'@nx/vite/plugins/nx-tsconfig-paths.plugin';import{TanStackRouterVite}from'@tanstack/router-vite-plugin';import{join}from'path';exportdefaultdefineConfig({root:__dirname,cacheDir:'../../node_modules/.vite/apps/web',server:{port:4200,host:'localhost',},preview:{port:4300,host:'localhost',},plugins:[react(),TanStackRouterVite({routesDirectory:join(__dirname,'src/routes'),generatedRouteTree:join(__dirname,'src/routeTree.gen.ts'),routeFileIgnorePrefix:'-',quoteStyle:'single',}),nxViteTsPaths(),],// Uncomment this if you are using workers.// worker: {// plugins: [ nxViteTsPaths() ],// },build:{outDir:'../../dist/apps/web',reportCompressedSize:true,commonjsOptions:{transformMixedEsModules:true,},},test:{globals:true,cache:{dir:'../../node_modules/.vitest',},environment:'jsdom',include:['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],reporters:['default'],coverage:{reportsDirectory:'../../coverage/apps/web',provider:'v8',},},}); |
BetaWas this translation helpful?Give feedback.
All reactions
👍 4👀 1
-
I've set this up but was wondering how would i allow other libraries to share the types for my router so when using navigate() or Link i have full type saftey. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
@rahulretnan do you find the watch still works? as soon as I add in a root: __dirname it regens fine on startup but seems to fail on changes. |
BetaWas this translation helpful?Give feedback.
All reactions
-
Hi@tl-frameplay@rahulretnan , can you please help me in figuring out how routing will work in Microfrontend architecture.. I am using turborepo and tanstack Router |
BetaWas this translation helpful?Give feedback.
All reactions
-
I'm also using turborepo and trying to figure out how to do this. Currently we have a number of packages that are router agnostic and one app package that declares the router module. Now I'm trying to split up that app into multiple packages that all use the same router with type checking, but I'm not sure how to do it without creating a circular dependency between the packages. I've tried a few hacky things that didn't work and will continue trying, but if anyone has a solution to this it would be great to hear. We're not using file based routing so TanStackRouterVite isn't an option. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 13
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I'm starting to tackle this exact problem in our app. We are also using a monorepo setup. We have a top level "app" package that imports lots of other packages that are more focused on components, specific business logic, etc. We want the lower level library packages to be able to participate in Tanstack router's type safety. The problem is that we've defined all of the routes on the "app" level and the children packages cannot take a dependency on "app" because, well, circular dependencies and all the fun that comes with that. But since we only care about making the types available to all children packages, I'm exploring adding a small typescript task that just outputs the route's Still working out how this plays out long term but its very promising so far. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
I guess,@tannerlinsley finally heard us.😅 He is including a example of tanstack router in monorepo. Thanks, Really, appreciate the Effort. ✨ Basic: With React Query: https://github.com/TanStack/router/tree/main/examples/react/router-monorepo-react-query |
BetaWas this translation helpful?Give feedback.
All reactions
❤️ 1
-
actually this was added by@beaussan :) |
BetaWas this translation helpful?Give feedback.
All reactions
❤️ 11
-
I gave it a go and made a turborepo template with tanstack router at the core: It's not a small example, with many other components such as
and so on, but hopefully someone finds it helpful :). |
BetaWas this translation helpful?Give feedback.
All reactions
❤️ 5🚀 1
-
this is awesome. nice job man! |
BetaWas this translation helpful?Give feedback.
All reactions
❤️ 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I'm going to hijack this discussion post to try to explain my main difficulty with the current recommendation of how Router supports monorepos, and what I think some potential solutions are. It all revolves around cyclical dependencies (get it?). What do I mean? Lets say I have the monorepo setup that's currently in the docs: The main thing to note here is that Now lets imagine that in Now lets add a component from Lets say that this this component is the actual thing that cares about our search param. These typed & validated search params are a really powerful feature of Router. Personally, I really like the idea of having component that require / consume these params be responsible for getting them, similar to other ideas of having components that consume specific data be responsible for getting that data. Router thought of that too, and it's great that you can access to these typed & validated values even outside of route components via the Except wait a second, how does But now we've created a cyclical dependency: Big no-no in monorepos, most tools will yell at you if you have cyclical dependencies, if notoutright refuse to run. So how to solve this? Well there's a few ideas I can think of: 1). Router concerns are router concerns. Leave the router stuff to the router and have packages expect that data via props / another data contract: I'm totally cool with this being the answer. It's not what Iwant, but it makes sense. Now our graph has the correct direction again: 2). Loosen types Loosen up our types as outlined in the docs: I'm down to loosen these types, and type them inside our package instead. But we're better than that, right? Ok cool, moving on. 3). Create a new sink for the Route APIs in our graph Go onto any of these GitHub issues where people complain about cyclical dependencies and the first answer is to create a new package or combine existing packages. We're not gonna combine But Router doesn't really support this at the moment. When I generate my types for the 4). Import the package in the app only This is how the current monorepo example is set up. In this paradigm, With file based routing, I really like how we can look at our files in our routes folder and understand the structure of the app at a glance, AND seeing the actual page rendered on the route. This colocation of pages and routes is really powerful and is a big reason for me why these file based routes are so powerful. I think by spreading them out like this we've taken the idea of the monorepo too far. I want my router to be responsible for pages and routing, not just routing. Maybe this means that we do need to go back to the single responsibility pattern outlined in solution 1, I'm not sure. All of this is to say that something about splitting my pages from my router feels off to me. I'm open to being wrong on this, but I feel like I'm losing a key advantage of what I like about file based routing. 5). Cheat This is kind of what@davidturissini mentioned in their reply above, which is to just grab thetypes via a script or task and make them available at a lower package in the graph. I'd go even further honestly, I'd be fine with putting something like that at the root of the repo, something like a My preferred solution Someone tell me if I'm missing something or doing something wrong, because I've found it difficult to get my sweet, sweet type safety with Router in a monorepo. Idk, let me know what you think or if I'm over complicating this. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Thank you@taylorfsteele for this suggestion! I added a step to the types package that bundles the app types and then imports that. This avoids having to type check the main app from each feature package, and allows the main app to have a different tsconfig/path aliases. import{defineConfig}from"tsdown";exportdefaultdefineConfig((options)=>({dts:{emitDtsOnly:true,},clean:!options.watch,entry:["../../apps/web/src/router.tsx"],outDir:"../../apps/web/dist-types",tsconfig:"../../apps/web/tsconfig.app.json",})); // packages/web-router/src/index.ts// gets compiled by tsc and replicates the standard router re-export from the monorepo exampleimport{useRouteContext}from"@tanstack/react-router";importtype{RouterIds,RouterType,SaasRouterContext}from"../../../apps/web/dist-types/router";exporttype{RouterIds,RouterType,SaasRouterContext};exportconstuseSaasRouterContext=()=>{constcontext:SaasRouterContext=useRouteContext({from:"__root__"});returncontext;};exporttype{ErrorComponentProps,RegisteredRouter,RouteById}from"@tanstack/react-router";// By re exporting the api from TanStack router, we can enforce that other packages// rely on this one instead, making the type register being appliedexport{createLazyRoute,ErrorComponent,getRouteApi,Link,Outlet,RouterProvider,useLocation,useNavigate,useParams,useRouteContext,useRouter,useSearch,}from"@tanstack/react-router"; Build with import{getRouteApi}from'@acme/web-router'; Caching builds is a little weird because the router types can depend on feature packages. but it is much easier to build out features without having to split them between the router and app sides. It also makes it feasible to add Tanstack Start/automatic code splitting, which I haven't figured out how to support in the existing monorepo pattern. |
BetaWas this translation helpful?Give feedback.
All reactions
-
I've been stewing on this for a while, and the more I think about it, the more I think I prefer your solution,@alexjball. I think the thing for me is that as the project grows, and the types become a bit more involved, I really don't like how my solution causes cyclical typechecks (e.g., when I run More to the point of this whole discussion however, this might be where TanStack Router can help. Router already does some amount of codegen types via I'll fiddle with this, but I do think that generating these types in a single build step is better than my solution of slowing down typechecking throughout the project. I'll report back if I find anything to help with caching & maybe improving the DX. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Finally got around to fixing this after I added a new package and my typecheck step took twice as long 😓 I went ahead with@alexjball 's approach and used a build to reach across package boundaries to get the right types. I don't quite see the valueyet in exporting the actual I ran into errors when trying to correctly build out the router, the build step wasn't getting the types for my In my case I'm not using any path aliasing (I find they're too annoying inside monorepos), so I had to tell tsdown where to find these correctly. Your milage may vary. The last step was making sure that my types package had access to I'm consuming it just like before, where my other packages install this types package as a dev dependency and reference it inside their tsconfig: Types performance is way better now, at the tradeoff of having to run a build task in order to get the types correct. I think that's a fine tradeoff, your favorite monorepo tools can help you manage that build step. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
@taylorfsteele Hi Taylor, I've been following this discussion for some time, and in pretty much similar boat. Would really appreciate a poc in action just so we can experiment. Thanks! 🙌 |
BetaWas this translation helpful?Give feedback.
All reactions
-
@ifxnas Did you ever get this working? I can try to put up a full repo as an example to help, but it really just would be the steps I outlined above. I'm still using tsdown & built types solution and it's... fine. I'm using Turborepo as my monorepo tool, and it's pretty easy to set up a graph that runs my Type performance still feels a little sluggish to me, but I think that has more to do with the multiple layers of inference in the repo. The giant |
BetaWas this translation helpful?Give feedback.
All reactions
-
Hey everyone, My structure looks like this: The main thing I’m aiming for is that Has anyone found a good way to do this with the current state of TanStack Router? |
BetaWas this translation helpful?Give feedback.
