router: Slow TS performance

Describe the bug

I have a fairly small project (it will grow), right now there are about ~20 pages, but I am already facing some performance issues after including the router package, mainly in the IntelliSense and auto-complete. After some investigations, I have narrowed down part of the issue to be that for every component I use navigate from useNavigate, I get a bad compilation time for that file.

I have identified this using --generateTrace flag for tsc, it points to every single one of these files to the particular line of when the navigate(...) method is being used.

npx @typescript/analyze-trace trace
Hot Spots
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/policiesaddedit/policiesaddedit.tsx (1050ms)
├─ Check file /users/me/dev/project/packages/web/ui/src/components/sidebar/sidebar.tsx (1001ms)
│  └─ Check deferred node from (line 70, char 9) to (line 82, char 16) (979ms)
│     └─ Check expression in /users/me/dev/project/applications/web/dashboard/src/router/index.tsx from (line 84, char 24) to (line 84, char 30) (651ms)
│        └─ Check expression from (line 71, char 16) to (line 77, char 3) (651ms)
│           └─ Check expression from (line 71, char 27) to (line 77, char 2) (649ms)
│              └─ Check expression from (line 72, char 5) to (line 72, char 14) (642ms)
│                 └─ Check expression from (line 32, char 19) to (line 64, char 3) (626ms)
│                    └─ Check expression from (line 32, char 41) to (line 64, char 2) (626ms)
│                       └─ Check expression from (line 34, char 5) to (line 63, char 7) (444ms)
│                          └─ Check expression from (line 34, char 36) to (line 63, char 6) (435ms)
│                             └─ Check expression from (line 35, char 9) to (line 62, char 11) (435ms)
│                                └─ Check expression from (line 35, char 37) to (line 62, char 10) (430ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/signup/index.tsx (814ms)
│  └─ Check deferred node from (line 54, char 9) to (line 223, char 16) (735ms)
│     └─ Check expression from (line 55, char 13) to (line 58, char 44) (732ms)
│        └─ Check expression from (line 57, char 22) to (line 57, char 46) (731ms)
│           └─ Check expression from (line 57, char 23) to (line 57, char 45) (731ms)
│              └─ Compare types 137362 and 137370 (731ms)
│                 └─ Check expression from (line 39, char 20) to (line 48, char 19) (731ms)
│                    └─ Check expression from (line 39, char 20) to (line 45, char 23) (730ms)
│                       └─ Check expression from (line 39, char 20) to (line 44, char 96) (730ms)
│                          └─ Check expression from (line 44, char 23) to (line 44, char 95) (730ms)
│                             └─ Check expression from (line 44, char 29) to (line 44, char 95) (730ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/login/index.tsx (795ms)
│  └─ Check deferred node from (line 57, char 9) to (line 124, char 16) (725ms)
│     └─ Check expression from (line 58, char 13) to (line 61, char 44) (719ms)
│        └─ Check expression from (line 60, char 22) to (line 60, char 46) (718ms)
│           └─ Check expression from (line 60, char 23) to (line 60, char 45) (718ms)
│              └─ Compare types 134742 and 134800 (717ms)
│                 └─ Check expression from (line 34, char 20) to (line 51, char 14) (717ms)
│                    └─ Check expression from (line 38, char 17) to (line 50, char 18) (716ms)
│                       └─ Check expression from (line 45, char 32) to (line 45, char 113) (716ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/apikeysedit/index.tsx (703ms)
│  └─ Check deferred node from (line 35, char 9) to (line 65, char 10) (553ms)
│     └─ Check expression from (line 41, char 20) to (line 64, char 15) (553ms)
│        └─ Check expression from (line 58, char 21) to (line 64, char 14) (537ms)
│           └─ Check expression from (line 59, char 24) to (line 63, char 19) (537ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/accountedit/index.tsx (615ms)
│  └─ Check deferred node from (line 33, char 32) to (line 43, char 6) (516ms)
│     └─ Check expression from (line 36, char 13) to (line 36, char 62) (515ms)
│        └─ Check expression from (line 36, char 19) to (line 36, char 62) (515ms)
└─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/resolversinstancesaddedit/resolversinstancesaddedit.tsx (513ms)
   └─ Check deferred node from (line 55, char 9) to (line 72, char 10) (377ms)
      └─ Check expression from (line 58, char 17) to (line 61, char 40) (373ms)
         └─ Check expression from (line 58, char 17) to (line 61, char 25) (373ms)
            └─ Check expression from (line 58, char 17) to (line 61, char 19) (373ms)

I am using the navigate like (example from SignUp):

export default function SignUpPage() {
    const navigate = useNavigate({ from: '/sign-up' });

    const onSubmit = useCallback(
        async (data: SignUpSchemaType) => {
            // auth code
                .then(() => navigate({ to: '/verify-account', search: { email: data.email } }))
        },
        [context.auth],
    );

From the example, on the line with navigate I get the hotspot from the report.

Is there anything I can do to help out the compiler for faster resolution? Maybe if there are some recommendations, it would be helpful to add on the docs site.

Your Example Website or App

/

Steps to Reproduce the Bug or Issue

  1. Import via useNavigate
  2. Use method navigate
  3. Slow TS compile

Expected behavior

Fast TS compile

Screenshots or Videos

No response

Platform

  • OS: MacOS 2019, Intel i9, 16GB RAM
  • Browser: [e.g. Chrome, Safari, Firefox]
  • Version: 1.14.0

Additional context

I have may other instnaces where navigation is made via Link and those instances doesn’t seem to cause slow compilation. Maybe because they are JSX?

About this issue

  • Original URL
  • State: closed
  • Created 5 months ago
  • Reactions: 3
  • Comments: 25 (12 by maintainers)

Most upvoted comments

I was looking into using Link with a large number of routes. I wonder if this is the same issue I noticed.

Regarding what I looked into when tracing. I found possibly something interesting:

RelativeToPathAutoComplete has SplitPaths which splits all paths regardless as a defaulted type parameter. If you make SplitPaths inline so it’s only evaluated when it’s needed and then when the ‘from’ prop is not specified (TFrom is defaulted to string) then return all paths without splitting all the paths. This skips the crazy splitting of a large union of paths. With these changes I noticed performance improvements when not using the ‘from’ prop

I tried this with 600 odd routes and check time went from 12.69s (with excessively deep instantiation errors) to 2.49s (with no errors)

useNavigation is more complicated. It’s defaulting TFrom to all paths and this is harder to check for (not sure why it’s defaulted this way?). TS seems to checking a large union here as well. I was able to get similar performance as Link if TFrom is defaulted to string and some other odd changes

This doesn’t fix performance issues when the ‘from’ prop is specified. There might be a better way to work out the relative paths. But that requires more work and thinking how to approach it. I would maybe try to avoid parsing of huge unions and try to narrow it down

I usually try not to iterate over large unions unnecessarily and minimise checking against them.

I’m happy to contribute if you think it’s worth speeding up the non-relative path use case for now

Optimizations in this area are very welcome, so please, if you find something out, let us know. Even if those changes would be breaking, we might consider them for v2 if the TS compiler performance can be improved significantly.

Are you using file-based routing? if no, can you try it out and also trace it in comparison?

I have tried to migrate to file-based routing, and the performance is even worse. For example for the file of policiesaddedit what used to be ~1000ms now is at ~2000ms

Here is the experience with InteliSense experience when the file doesn’t have the Router interaction. As you can see, the suggestions are being shown as I type, immediately when i click the shortcut to show suggestions.

https://github.com/TanStack/router/assets/1628876/43456be9-229a-42f4-b381-ba46888c3cf1

Here is the experience when the component has Router interaction. Must note that this is very simple component that even here the experience is not that bad. In my policiesaddedit component it takes ~5s to create the suggestions.

https://github.com/TanStack/router/assets/1628876/da5640c7-6455-4c60-9fe8-3a6dbd938552


Here it is the hotspot log:

Hot Spots
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/policiesaddedit/policiesaddedit.tsx (2052ms)
│  ├─ Check deferred node from (line 95, char 9) to (line 111, char 10) (941ms)
│  │  └─ Check expression from (line 98, char 17) to (line 100, char 18) (940ms)
│  │     └─ Check expression from (line 98, char 17) to (line 98, char 109) (940ms)
│  │        └─ Check expression from (line 98, char 17) to (line 98, char 103) (939ms)
│  └─ Check variable declaration from (line 94, char 11) to (line 113, char 6) (883ms)
│     └─ Check expression from (line 94, char 22) to (line 113, char 6) (883ms)
│        └─ Check expression from (line 112, char 9) to (line 112, char 66) (883ms)
│           └─ Compare types 43842 and 53223 (881ms)
│              ├─ {"id":43842,"kind":"AnonymousType","location":{"path":"/users/me/dev/project/node_modules/.pnpm/@tanstack+react-router@1.14.0_react-dom@18.2.0_react@18.2.0/node_modules/@tanstack/react-router/dist/esm/usenavigate.d.ts","line":9,"char":5}}
│              └─ {"id":53223,"kind":"AnonymousFunction","location":{"path":"/users/me/dev/project/applications/web/dashboard/src/pages/policiesaddedit/policiesaddedit.tsx","line":64,"char":9}}
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/signup/index.tsx (1919ms)
│  └─ Check deferred node from (line 54, char 9) to (line 223, char 16) (1790ms)
│     └─ Check expression from (line 55, char 13) to (line 58, char 44) (1784ms)
│        └─ Check expression from (line 57, char 22) to (line 57, char 46) (1783ms)
│           └─ Check expression from (line 57, char 23) to (line 57, char 45) (1783ms)
│              └─ Compare types 33165 and 33208 (1783ms)
│                 └─ Check expression from (line 39, char 20) to (line 48, char 19) (1782ms)
│                    └─ Check expression from (line 39, char 20) to (line 45, char 23) (1782ms)
│                       └─ Check expression from (line 39, char 20) to (line 44, char 96) (1782ms)
│                          └─ Check expression from (line 44, char 23) to (line 44, char 95) (1780ms)
│                             └─ Check expression from (line 44, char 29) to (line 44, char 95) (1780ms)
│                                └─ Check expression from (line 44, char 38) to (line 44, char 94) (574ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/login/index.tsx (1897ms)
│  └─ Check deferred node from (line 60, char 9) to (line 127, char 16) (1766ms)
│     └─ Check expression from (line 61, char 13) to (line 64, char 44) (1762ms)
│        └─ Check expression from (line 63, char 22) to (line 63, char 46) (1762ms)
│           └─ Check expression from (line 63, char 23) to (line 63, char 45) (1762ms)
│              └─ Compare types 36665 and 33208 (1762ms)
│                 └─ Check expression from (line 37, char 20) to (line 54, char 14) (1761ms)
│                    └─ Check expression from (line 41, char 17) to (line 53, char 18) (1760ms)
│                       └─ Check expression from (line 48, char 32) to (line 48, char 113) (1760ms)
│                          └─ Check expression from (line 48, char 41) to (line 48, char 112) (575ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/routes/__root.tsx (1572ms)
│  └─ Check variable declaration from (line 42, char 11) to (line 49, char 7) (1226ms)
│     └─ Check expression from (line 42, char 29) to (line 49, char 7) (1225ms)
│        └─ Check expression in /users/me/dev/project/applications/web/dashboard/src/router.ts from (line 22, char 24) to (line 22, char 30) (765ms)
│           └─ Check expression from (line 11, char 16) to (line 17, char 3) (764ms)
│              └─ Check expression from (line 11, char 29) to (line 17, char 2) (763ms)
│                 └─ Check expression from (line 12, char 5) to (line 12, char 14) (762ms)
│                    └─ Check expression in /users/me/dev/project/applications/web/dashboard/src/routetree.gen.ts from (line 264, char 26) to (line 297, char 3) (759ms)
│                       └─ Check expression from (line 264, char 48) to (line 297, char 2) (759ms)
│                          └─ Check expression from (line 265, char 3) to (line 290, char 5) (716ms)
│                             └─ Check expression from (line 265, char 25) to (line 290, char 4) (545ms)
│                                └─ Check expression from (line 266, char 5) to (line 289, char 7) (545ms)
│                                   └─ Check expression from (line 266, char 37) to (line 289, char 6) (539ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/resolversinstancesaddedit/resolversinstancesaddedit.tsx (1552ms)
│  └─ Check deferred node from (line 55, char 9) to (line 72, char 10) (1178ms)
│     └─ Check expression from (line 58, char 17) to (line 61, char 40) (1176ms)
│        └─ Check expression from (line 58, char 17) to (line 61, char 25) (1176ms)
│           └─ Check expression from (line 58, char 17) to (line 61, char 19) (1176ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/accountedit/index.tsx (1224ms)
│  └─ Check deferred node from (line 33, char 32) to (line 43, char 6) (1119ms)
│     └─ Check expression from (line 36, char 13) to (line 36, char 62) (1119ms)
│        └─ Check expression from (line 36, char 19) to (line 36, char 62) (1118ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/policiesdetails/policiesdetails.tsx (966ms)
│  └─ Check deferred node from (line 37, char 31) to (line 37, char 85) (845ms)
│     └─ Check expression from (line 37, char 37) to (line 37, char 85) (845ms)
├─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/apikeysdetails/apikeysdetails.tsx (963ms)
│  └─ Check deferred node from (line 38, char 31) to (line 38, char 85) (854ms)
│     └─ Check expression from (line 38, char 37) to (line 38, char 85) (854ms)
└─ Check file /users/me/dev/project/applications/web/dashboard/src/pages/apikeysedit/index.tsx (901ms)
   └─ Check deferred node from (line 35, char 9) to (line 65, char 10) (776ms)
      └─ Check expression from (line 41, char 20) to (line 64, char 15) (776ms)
         └─ Check expression from (line 58, char 21) to (line 64, char 14) (774ms)
            └─ Check expression from (line 59, char 24) to (line 63, char 19) (774ms)

Taking as example policiesaddedit, here are the lines that are mentioned:

const onSubmit = useCallback(
        async (data: PolicyFormData) => {
            try {
                const res = props.type === 'add' ? await handleAddSubmit(data) : await handleEditSubmit(data);
// Line 98 bellow with `navigate`
                navigate({ to: '/iam/policies/$policyId', params: { policyId: res.policy.policyId } }).catch(
                    console.error,
                );
            } catch (e) {
                if (e instanceof Error) {
                    if (e.cause instanceof ZodError) {
                        form.setError('root', { message: fromZodError(e.cause).toString() });
                    } else {
                        form.setError('root', { message: e.message });
                    }
                }
                console.error('error creating policy', e);
            }
        },
// Line 112 bellow with the `navigate`
        [props.type, navigate, handleAddSubmit, handleEditSubmit],
    );

I do not have any circular dependencies between the code, route definitions, and generated route tree.

Here is gist with my generated route tree: https://gist.github.com/toteto/e7d7c1fee9f9a4bda315170758c29bd5

Cool. Yeah. I noticed that. It will work. It might be worth getting tests around these types to stop those sorts of regressions

@schiller-manuel I have some improvements for relative paths as this is still using Split and slower than it could be. Should I raise another issue for this? At least relative pathing should be faster.

I raised a PR. Looking into further improvements but this is a good start I think

https://github.com/TanStack/router/pull/1202