from Hacker News

One year with Next.js App Router and why we're moving on

by nnx on 10/30/25, 2:48 AM with 103 comments

  • by gherkinnn on 10/30/25, 7:15 AM

    Vercel has been a string of puzzling decisions since the introduction of the app router. Next could've become JS' Rails, instead it is a pile of confusing mess. Turbopack, caching, middleware (now called "proxy"), their layout components are silly, implementation of RSC, pushing for unfinished alpha versions of everything. Next is a conceptual mess of initialisms (RSC, SSR, PPR, SSG, ISG). Hosting integrations are semi-proprietary and they reliably break basic JS APIs like fetch and redirects.

    And despite all of that, they don't ship the basics that every app needs, like i18n and auth.

    Next should no longer be chosen under any circumstances.

  • by figassis on 10/30/25, 5:47 AM

    Around 8y ago, when Angular vs React was still a war worth reading, frameworks were I think in their final state. They gave you basic tools, and you could build applications with them. I felt like framework creators didn’t treat us like babies who needed handholding. Idk if a new generation of younger developers took over, but things started becoming too shiny. Blog posts were no longer about performance, ease of use, same solutions. I couldn’t even understand some post titles. There is just no bandwidth to follow these things anymore. Why is a router a thing that needs to be continually rebuilt and tinkered with. Did we not learn ages ago how routers should work? What innovation are we seeking? Is it just developers treating frameworks like their weekend experiments?
  • by PebblesHD on 10/30/25, 4:10 AM

    I have yet to reach the limits of doing a Vite create and installing react router myself for the several entirely client side apps we manage. It has sane build defaults and for whatever definition of ‘works’ is possible in JS, ‘just works’. If it becomes too complex for that basic setup it usually means we’ve over-complicated something.

    Where we have a need for server side, nodejs just never felt natural for us so we stuck with java springboot or flask/fastapi as appropriate.

  • by sibeliuss on 10/30/25, 4:06 AM

    Our experience entirely. We replaced next.js with a simple router and everything in every sense got simpler, and FASTER. It was a remarkable education, replacing that crazy thing.
  • by zenethian on 10/30/25, 4:33 AM

    It feels like so much work has been done to just end up going full circle back to Django-style website applications. All of these frameworks have continually re-solved problems that were already solved in something other than Javascript, and then people write blogs about how they're surprised about it. It feels a bit uncanny to see.
  • by zgoscenka on 10/30/25, 7:24 AM

    For a project at work we chose next because the team knew react and other people in the company know react. It is quickly becoming the worst architectural decision in my whole carrier. I wish we would've picked anything else...
  • by mosselman on 10/30/25, 7:32 AM

    The horror of needing to replace a routing layer. Why is this not a solved problem?

    This is an undervalued advantage of using steady frameworks like Rails that in essence is the same as 20 years ago, but with lots of extras. I don’t remember any big changes in the routes at least. Nor in any of the other basic building blocks.

    You could come back to rails after a 10 year break and pick up pretty quickly where you left off

  • by kobalsky on 10/30/25, 2:49 PM

    why would you go through the trouble of doing SSR on a user profile form?

    it's not needed for SEO, caching it is pointless, the server won't render it faster than the client so you are not speeding up anything.

    what's the point of complicating anything about it? seems that at least some of their suffering is a self-inflicted wound, maybe the author just picked a bad example.

  • by sanskarix on 10/30/25, 5:50 AM

    The issue is everyone's optimizing for blog post metrics, not actual problems. "Look at this new pattern!" gets clicks. "We kept it simple and it just works" doesn't. Same thing happened with microservices - everyone rushed in because it sounded cool, then spent years dealing with distributed systems hell.
  • by skeptrune on 10/30/25, 3:55 AM

    >This is because when Next.js loads the actual Server Component, no matter what, the entire page re-mounts. I was begging Next.js to just update the existing DOM and preserve state, but it just doesn't.

    YES! YES! I FEEL SO SEEN RIGHT NOW! I find this behavior unbelievably frustrating. It's hard for me to understand why they ever even shipped RSC's without fixing this.

  • by novoreorx on 10/30/25, 8:16 AM

    I noticed the author mentioned the stack they are currently using is NextJS + Hono, and according to the code in the blog, Hono is used as the API backend to provide data for NextJS through calls like `fetchUserInfo`. This is where I really got confusing, why is it designed like this? NextJS is already a full-stack framework, not to say you used RSC, you can directly get user data from the database in NextJS. If you decide to use Hono for HTTP API, which is fine because it's more light weight and adaptive, then why RSC rather than making the frontend a SPA? Even if you use NextJS to write SPA it would looks much reasonable to have both NextJS and Hono used together.
  • by para_parolu on 10/30/25, 4:07 PM

    I had to fully deattach routing and lazy loading because it was very slow and uncomfortable. Nextjs is well suited for half-static website that you need tomorrow. But once you get to something complex it starts showing so many issues.
  • by huksley on 10/30/25, 8:25 PM

    App router is an overengineered mess. "These rules are too hard for normal developers to understand."

    Just use pages router, it is still supported and works, also static generation for SEO also works.

  • by adverbly on 10/30/25, 3:37 PM

    It's crazy that NextJS became the default new developer tooling.

    It's a bit unfortunate that whoever can scream the loudest with their marketing to get people to git clone their framework tends to grow the fastest.

    There are so many layers of abstraction that are simply not necessary in most frameworks and that risk over complicating or under complicating many products.

    It's almost like blindly defaulting towards any opinionated solution is not what we should be teaching people to do... /Sarcasm

  • by bni on 10/30/25, 6:35 AM

    I use the Pages router. Doing it this way make sense to me in a way that SPAs never did.
  • by chuliomartinez on 10/30/25, 6:11 AM

    Tanstack lack of back button handling is infuriating. Throwing away users data is simply not acceptable.

    Is Wouter better in this regard?

  • by Zaheer on 10/30/25, 6:06 AM

    The biggest facepalm moment I had was when we switched Levels.fyi from gulp.js to next.js. Our pagespeed, hosting costs, etc all took a significant hit. We're experiencing the same issues as described in the post and weighing our options to transition as well. Avoid next.js / vercel at all costs.
  • by aiiizzz on 10/30/25, 11:40 AM

    Tanstack, last I checked, doesn't even support RSC.
  • by sktrdie on 10/30/25, 10:27 AM

    I'll try to review the article with comments to make this a more critical discussion instead of just hates on Next.js (I'm just a Next.js developers for years now and am quite happy with it - but I do agree it requires some deeper understanding)

    > React is now using the words "server" and "client" to refer to a very specific things, ignoring their existing definitions. This would be fine, except Client components can run on the backend too

    It was hard discussions to come up for naming of these things in the beginning. Even calling them "backend" and "frontend" (as they suggest in article) wasn't clear about their behavior semantics. I understand the naming annoyances but it's a complex issue that requires lots more thought than just "ah we should've called it like this"

    > …This results in awkwardly small server components that only do data fetching and then have a client component that contains a mostly-static version of the page.

    > // HydrationBoundary is a client component that passes JSON > // data from the React server to the client component. return <HydrationBoundary state={dehydrate(queryClient)}> <ClientPage /> </HydrationBoundary>;

    It seems they're combining Next's native hydration mechanism with TenStacks (another framework) in order to more easily fetch in the browser?

    To follow on their WebSocket example where they need to update data of a user card state when a Websocket connection sends data. I don't see what would be the issue here to just use a WebSocket library inside a client component. I imagine it's something you'd have to do to in any other framework, so I don't understand what problem Next.js caused here.

    What they're doing screams like a hack and probably the source their issues in this section.

    > Being logged in affects the homepage, which is infuriating because the client literally has everything needed to display the page instantly

    I'm not sure I understand this part. They mention their app is not static but instead is fully dynamic. Then, how would they avoid NOT showing a loading state in between pages?

    > One form of loading state that cannot be represented with the App Router is having a page such as a page like a git project's issue page, and clicking on a user name to navigate to their profile page. With loading.tsx, the entire page is a skeleton, but when modeling these queries with TanStack Query it is possible to show the username and avatar instantly while the user's bio and repositories are fetched in. Server components don't support this form of navigation because the data is only available in rendered components, so it must be re-fetched.

    You can use third-party libs to achieve this idea of reusing information from page to another. Example of this is motion's AnimatePresence which allows smooth transitions between 2 react states. Another possibility (of reusing data from an earlier page) is to integrate directly into Next.js new view transitions api: https://view-transition-example.vercel.app/blog <- notice how clicking on a post shows the title immediately

    > At work, we just make our loading.tsx files contain the useQuery calls and show a skeleton. This is because when Next.js loads the actual Server Component, no matter what, the entire page re-mounts. No VDOM diffing here, meaning all hooks (useState) will reset slightly after the request completes. I tried to reproduce a simple case where I was begging Next.js to just update the existing DOM and preserve state, but it just doesn't. Thankfully, the time the blank RSC call takes is short enough.

    This seems like an artefact of the first issue: trying to combing two different hydration systems that are not really meant to work together?

    > Fetching layouts in isolation is a cute idea, but it ends up being silly because it also means that any data fetching has to be re-done per layout. You can't share a QueryClient; instead, you must rely on their monkey-patched fetch to cache the same GET request like they promise.

    Perhaps the author is missing how React cache works (https://react.dev/reference/react/cache) and how it can be used within next.js to cache fetches _PER TREE RENDER_ to avoid entirely this problem

    > This solution doubles the size of the initial HTML payload. Except it's worse, because the RSC payload includes JSON quoted in JS string literals, which format is much less efficient than HTML. While it seems to compress fine with brotli and render fast in the browser, this is wasteful. With the hydration pattern, at least the data locally could be re-used for interactivity and other pages.

    Yes sending data twice is an architecture hurdle required for hydration to work. The idea of reusing that data in other pages was discussed before via things like AnimatePresence.

    What's important to note here is that the RSC payload exists at the bottom of the HTML. Since HTML is streamed by default this won't impact Time-to-first-Render. Again, other frameworks need to do this as well (in other ways but still, it needs to happen)

    I totally understand the author's frustrations. Next.js isn't perfect, and I also have lots of issues with it. Namely I dislike their intercept/parallel mechanism and setting up ISR/PPR is a nightmare. I just felt like the need to address some of their comments so maybe it can help them?

    As a first I would get rid of tanstack since it's fighting against Next.js architecture.

    Or yeah just move entirely elsewhere :)

  • by 383toast on 10/30/25, 4:09 AM

    ...or just not use server components :)
  • by mayaj47 on 10/30/25, 2:55 AM

    have you tried Nuxt.js
  • by tock on 10/30/25, 7:55 AM

    I've used the app router and it is fairly nice once you understand how all of it works. But its a bucket load of complexity nobody really needs. A normal react SPA using vite + wouter + react-query works brilliantly.