995: Next.js Vendor Lock-in No More

š **Source:** [995: Next.js Vendor Lock-in No More](https://share.snipd.com/episode/e822a026-a0ac-440f-a574-f251c5330f5d)
## Highlights
- **Adapters API Unblocks MultiāPlatform Next.js**
- The Adapters API creates a stable integration layer so Next.js can be hosted across providers and runtimes without fragile hacks.
- Tim Neutkens built it with partners like Cloudflare and Netlify and dogfooded it on Vercel first to avoid breaking customer sites.
Transcript:
Tim Neutkens
Yeah. So, we launched the adapters like API as like stable, which has been like a long time coming. As you said, we've been working on it for close to a year at this point. And so the idea is that we can, next.js by itself, it runs super well with next.start, on the simple VPS, you can tap into all capacities of next. But when it comes to scaling it, it's always been maybe slightly more difficult, just because Next.js capacity is like the features like caching and ISR just requires like, you know, Synchronization across like, you know, multiple nodes if you need it. So, or with your CDN, if you require it. And we basically introduced a layer so that you can basically tap into this and, you know, have like a somewhat of a stable contract so that you can easily adopt it wherever you want to Host it. The adapters API is useful for yourself if you're just hosting just across five simple servers. You might not even want a VPN, sorry, a CDN or something. But it's also useful if you're someone like Cloudflare or Netlify and you're hosting Next.js in a specific way with serverless and you want potentially to host your middleware in a Different way. And so yeah, we created that API as a need to answer the needs of the community. We also created an ecosystem working group, which is basically composed of us and partners across Google, Netlify, Cloudflare. We used to have AWS in the group today. They dropped out at some point. But the idea is that we, since a lot of the Next.js features require that level of, you know, a certain level of infrastructure work to make it work. Yeah. We basically basically creating groups that we can like share ideas in advance, get collect their feedbacks and kind of, you know, make sure that they don't, you know, dislike supporting NextSets on their own platform. ([TimeĀ 0:01:20](https://share.snipd.com/snip/4c8d0788-6343-42b4-b9f5-9fbd7d2d7a53))
- **Vercel Dogfooded The New Adapter Internally**
- Next's new adapter was built from the ground up and dogfooded on Vercel to replace an older, battle-tested adapter.
- Tim describes the previous adapter dating back eight years and the careful rollout to avoid regressions on Vercel's large apps.
Transcript:
Speaker 1
Because for reference, think about this adapter that we had before. It had been battle-tested across eight years or so. Since we introduced NowV2, when we introduced serverless functions into Vercel, this was still the same adapter from back then. So all the possible edge cases that you could find in Vercel version of NextGES, this new adapter was from the ground up built on the adapters API. So we're really dogfooting this in the same way that every other platform is. And because of that, we had to do some extra checks to make sure that we can roll it out to everyone in this way. That's why the timeline was also spread out over a year instead of, hey, we have this new thing. Because in practice, if you think about it, the Adapters API inside of Nexus itself, that's not that complicated. It's a couple of functions that have a typed contract. The real point is you now have an integration layer that you don't have to reverse engineer on any side of things. ([TimeĀ 0:05:27](https://share.snipd.com/snip/c4d1273b-e9d4-439c-ae70-0862f1af97bf))
- **Build First Then Add Cache Components**
- Optimize later: build your app first, then progressively add caching with cache components when you need performance gains.
- Use useCache and suspense boundaries to turn dynamic behavior into incremental optimizations rather than default complexity.
Transcript:
Tim Neutkens
The thing is, something that I kind of want to try to dispel a little bit is that a lot of what we're talking about, the complexity that comes with some of the Next.js features, those are Mostly additive compared to the other frameworks. On its own, if you're just looking for a framework that just server-side renders or just serves API routes and serves some cached data, that stuff just kind of works out of the box. There's nothing. You don't need crazy infra or anything to do. So you just need a server that can return to your response. Where we go a bit further is when it comes to basically everything caching related. And we've made kind of updates to our docs to kind of clarify this. But those things are mostly, I'd say, performance optimizations. Right? Like partial pre-rendering, which is like one of the features that allows you to kind of serve like a static page and then kind of compose with some dynamic contents.
Scott Tolinski
I love those. I want those features. Those are great features. Yeah, yeah.
Tim Neutkens
Yeah, and it works with NextArk, right? You're going to hit your server. It's going to return, without rendering, it's just going to return you the static HTML, and then it's going to do the render, right? And when it comes to first-cell, the way we kind of optimize for this is that this now, this CDN, sorry, this HTML shell, can be served from the CDN rather, right, like closer to the user. And then we'll invoke the server in the background. This is purely just taking the primitives that we've taken, that we've put in the framework and then just optimizing them. I think that answered the question a little bit. I think to get the first cell-ish experience, what I would recommend anyone who's adopting or adapters to get those features is you want your server close to your database. If you're doing any research, it's the thing that makes sense in general. And then you want your static content always as close to the user as possible. And you need something that kind of handles the connection between the two. And it could be like on Cloudflare, it could be like a worker. And that worker would just execute edge, return to you the CDN content, the static shell, and then invoke another worker that is closer to the origin, basically.
Speaker 1
And even for this case, if you're talking about PPR, for example, this static shell, even if you serve that from a server that does dynamic renderings, if you serve it from next start, It's still a win, because you still have this immediate response. The only difference is that this response is not immediately coming from ACDN per se, but it's instead served from your server that you have hosted. And that's the same way if you go to any other platform. It works in the same way. But what you can do additively to that is you can make it so that your CDN supports this new primitive, like VPR, for example. And then it can serve that shell from the CDN itself while stitching the dynamic ([TimeĀ 0:08:23](https://share.snipd.com/snip/b7cd9618-305d-4c4f-890d-8e46d6adc139))
- **Shared Cache Invalidation Is Critical For Scale**
- Cache invalidation must be shared across nodes for revalidation to work correctly in scaled deployments.
- Tim calls out synchronization as a remaining area the Adapters API and platform partners need to improve.
Transcript:
Tim Neutkens
Let's say you're having five servers that are using Next. And you might start it with Next.start on all your instances. If you're Next.js caching, the pretty powerful thing about it is that we allow you to invalidate it whenever you want, and we do it in the background. And of course, whenever you call this revalidation method, one thing that is important for your provider or yourself is that a real validation is just shared across all nodes. And so that's one of the things that we want to... ([TimeĀ 0:11:55](https://share.snipd.com/snip/db6035c5-2550-43d3-b58f-216221261172))
- **UseCache Across Browser Server And CDN Layers**
- Think multilevel: useCache can apply to browser, runtime, build-time, and CDN layers to fit different page needs.
- Choose where to cache per-page: static CDN for infrequently changing pages or client cache for app-like pages such as ChatGPT.
Transcript:
Tim Neutkens
So it starts purely through the client. So if you use usecache, you can actually add a property that will just say cache it on the browser so that, across my session, the page is stored here, and I can configure how long it should Be kept around. Then it can also happen at runtime, when you server-side render your page. Now this cache can also live on your server, across your request, and you can add a time that you want to keep it around. We can also have this at build time and for static pages where now this cache page should live in your CDN too. And so there are multiple layers to it, but we think basically it allows you to build the most composable kind of app. Some pages, for example, yeah, it's fine if they live on the CDN, and they never really sort of revalidate. And if you do so, you're doing it kind of random, not randomly, but occasionally, like a CMS or something. But those things don't matter, if you're a page in an app like ChatGPT, where you would rather want to cache on the client instead. One thing I'm really excited about, for example, is that we could, we want to extend the use cache to like layers so that we can have also like an offline kind of layer. So right now, like we can cache it on clients across sessions. But the idea is like, what if we just like also kept it ([TimeĀ 0:13:45](https://share.snipd.com/snip/1afce504-8ed6-4c33-bced-37fc0b875f49))
- **Adapter Test Suite Powers Transparent Platform Support**
- The Next.js team publishes an adapter test suite and will mark adapters in docs with which tests/features they pass.
- Platforms can be 'blessed' by running the suite; results show which features (e.g., VPR) are supported rather than imposing a pass threshold.
Transcript:
Tim Neutkens
Yeah, it's a great question. Not really. So the idea is that I don't like I don't want to get keep necessarily on like whatever you like, you know, someone's platform is supporting or not. Sometimes the thing with PPR is that it does require a little bit of warming if you want to do this at scale. And there's no right and wrong here. It's more like just like the users kind of should make their own choice. But one thing we do require as part of the adapters working group, if they want to be blessed, is that they should just run our test route. So we've made some work to make this available for everyone. In theory, you just plug in your API keys and just the pipeline on how to just deploy it with a few scripts here and there. And then you can just use our test route in the same way Vinex has used it and just basically assess how good your platform is at supporting Next. And even then, it doesn't really cover, it's not very opinionated about whether or not you're running PPR from a CDN or not. We don't really sort of check for performance expectations here rather than just like, you load the page and it's just working as intended. But maybe it could be taking 20 seconds. I don't really care about this as long as the test pass.
Speaker 1
And the other thing is we're also not specifically gatekeeping on a amount of test passing. So what we did is we gave you the test suite. The only thing we ask, so there's two sets of adapters. There's adapters that anyone could build, and there's the ones that are part of the docs that we explicitly mention, and that we also publish the results from the test suite for. ([TimeĀ 0:20:03](https://share.snipd.com/snip/74a72995-892b-4ce7-b111-9edf2e67f4aa))
- **Abstract LoadāBearing APIs For CrossāRuntime Support**
- Design for runtime portability: build Next.js APIs independent of Node so platforms like Bun or Workers can implement equivalents.
- Tim says Next.js sells APIs not a specific runtime and will abstract load-bearing APIs when platforms lack equivalents.
Transcript:
Tim Neutkens
Yeah, that's the idea, right? Because as a user, I also hate this, right? What we're striving really hard within XJS experience is that devs start and when you push it on Vercel behaves exactly the same, like Debugging capacities are the same. And I just want to push that forward in the F2. What we're selling with Next.js by itself is not like, I'm not married to the idea of Node. I'm not married to WorkerD or Bund as a runtime itself. What we're selling is APIs, just nicely done APIs. And hopefully, a lot of the stuff we do, obviously, is very targeted towards Node. Just because that's the main default. ([TimeĀ 0:25:42](https://share.snipd.com/snip/cbc4fee9-c636-497e-a845-43c3bb93acd5))
- **Server Components Create A Recursive Compilation Problem**
- React Server Components create an interleaved compilation problem (serverāclient) that multiplies bundler runs and slows builds.
- TurboPack was designed to handle this orchestration and incremental compilation rather than patching multiple bundler instances.
Transcript:
Speaker 1
So we started building Trollpack, I can't remember which year. It's been some time. The reason we started building it is for context, we were using Webpack before, like most other frameworks at the time. Basically, Webpack, it has some limitations. The main limitation it has is that it's single-threaded. It's mostly JavaScript, not TypeScript. It has a very extensive plugin API, but really the biggest problem that we ran into as applications kept scaling. Think about it this way. If Wes or Scott starts building a new application today, they're probably pulling in 10x more code than they were 10 years ago when we started NextJS. So over time, basically you start hitting this problem of there's so much code to compile, now everything gets slower and slower. There were some takes on how to solve that problem, which included, and then like Next itself also made this problem-ish 2x worse by having two compilers. So you're not running one Webpack, you're running two Webpacks. Like one Webpack for the browser, like your browser and one Webpack for your server code. So for server set rendering and all of that. We had to do that because otherwise you can't run browser code that's bundled for the browser in the server. Your type of window would be replaced or things like that. You couldn't do as many optimizations, all of that. So basically what NexJS was doing was like, it was like trying to orchestrate multiple instances of Webpack and trying to make sure that they compile at the same time, they finish at The same time, they run in parallel as much as possible. But in practice, you can't really do that because like one might depend on the other. And then when you have React Server components, now you have this, like it's a scheduling problem, basically. Like you have use client, and use client could import a use server, like a server action. And this, like the first file that you find is a server component. So like that's server code. So that's running in the server compiler. But the moment it finishes compiling, it has found all the, like it collects all the use client, and then it injects it into this second WEPIC instance. And it's like, hey, you know what? Now we're going to compile that. And then when that second instance runs, and it finds all the use server, it now has to run the first compiler that we triggered again to then compile all this new code that we found. And basically that means that you're going from server, client, server, client, and it can effectively or recursively go through more than that. But we blocked you from doing that. In practice, you should be able to do that, though. ([TimeĀ 0:30:20](https://share.snipd.com/snip/7a322099-15d9-46ac-8adc-e7368330074b))
- **Favor Incremental Compilation For Developer Speed**
- Prioritize incremental compilation: accept a slower first build to gain cached, near-instant subsequent builds.
- TurboPack caches compilation state (like salsa in Rust) so large apps get much faster repeated builds and dev restarts.
Transcript:
Speaker 1
Because a lot of the, say, Vita's built on a lot of this knowledge of mistakes that were made for WebEx, for example, or things like that. So can we take all of these learnings from building all these different platforms, and then a lot of people that came into Vercel, like people that came from Svelte and all of that, take All of this knowledge and then build something new that takes learnings from everything that came before, similar to how most new open source projects are built. So that's where we started. And that's how we started building TurboPack. And then we had this, pretty quickly we had a fully functional bundler. You take inputs, you create outputs, it runs transforms, all of this. That was actually not the hard part. Building a bundler, we know how to do this and we hired all the right people to work on it. And we had that pretty quickly. So a create React App type app, we got to that point pretty quickly. The problem was that, and why it took quite some time to get fully to 100% of Nexus tests passing, is that Nexus has a lot of tests. I think 11,000 to 13,000 tests, if you take both dev and build. Cloudflare knows about those tests, yeah. So this test suite is super large, but it tests all edge cases. That includes things that bundlers would usually be testing. So it includes HMR. It includes fast refresh. It includes all of these edge cases, every bundler feature. Because when I was building Nextchase very early on, we were just like every single edge case that we were into, instead of just upstreaming every like test that we have for that, we also Added the test just in general to make sure that it keeps working in XS. Because like we were doing this orchestration thing, like I explained, and you would hit issues with the orchestration itself, which means that it's not a bundler issue. It's actually a nexus issue. So we already had this like very extensive test suite of like how a bundler should behave. We basically had to go through and fix every single edge case so that it behaves in the same way or super close to what it was doing before, including a lot of the behaviors that Webpack Has. So an example here is there's a comment called Webpack ignore true that you can add into an import. I'm not sure if you've ever seen it. That makes it ignore the import when bundling. A lot of NPM packages have that, for example. I had never seen it before myself, but as we started building this, obviously we're into it, because TurboPack would start bundling it, for example. I found out other frameworks also start, or other bundlers also start bundling it. But then in Webpack, it would like just ignore it, and like there would be no compiler error whatsoever. So we have like, in Turbo Pack, we have support for like a lot of these like Webpack specific APIs as well. And we make sure that like, for the most part, your application, like you don't have to rewrite like a lot of things to get to this newer version. So that covers this millions of people case. At the same time, what we've seen while we were building this, other vendors, they kept innovating as well. There was RSPack that tries to implement the whole Webpack API interface while giving you this parallelization and slightly better bundling and parallelization. And at the same time, also, you start a feed team build rolldown, which is really cool as well, which takes a lot of the same learnings that we had for TurboPack. So in practice, the way I feel about it today is that all of these vendors have started to converge onto a similar type of feature set. And the way they work is actually very similar. The only difference is trade-offs that you make around uh like caching uh like incremental compilation like things like that so for something i didn't talk about for throwback specifically Is like we went all in on this like incremental compilation architecture so similar to uh like salsa and rust or things like that which is like you can annotate functions that are cached Automatically and paralyzed automatically. And this was especially important for us because we were also seeing, besides the new apps are 10x larger, the existing apps became 10x larger as well. And we want this incremental compilation, like basically we want everything to be incremental compilation. So this means if you're in a build, like the first build, like the very first time might be slow or slower than like any other builder, for example. But after that, we have every single thing that it did cached. And if you make another build, it's a lot faster. ([TimeĀ 0:37:24](https://share.snipd.com/snip/c3fdacc4-a68e-43f5-a7cd-7431a3a9c95f))
- **TurboPack Is Usable Now But API Is Intentional**
- TurboPack lives in the Next.js repo but is a standalone bundler with a CLI; a public plugin API is pending careful design.
- The team already runs TurboPack by default in ~92% of Next.js dev sessions on Next.js 16.
Transcript:
Scott Tolinski
Are other projects using Turbo Pack or is this just a Next thing now? Right now it's part of the Nexus repository. It is a fully standalone tool that you can run.
Speaker 1
The only thing is that we don't have added, like we haven't added a public API for this yet. The reason for that is that we wanted to really prove out that this bundler has a right to exist, that it can run on these Nexus applications and that it doesn't have any bugs on bundling NPM packages and things like that. We're past that point at this point, and we do have a CLI for it now. The only thing is figuring out what the public API looks like. And then especially one of the things that's missing is we can run WAPIC loaders, for example, and things like that. We don't have a plugin API specifically for TurboPack right now. And that's what we're currently exploring as well, what that will look like. Then there shouldn't be a reason that you can't run TurboPack. But right now, if you wanted to use it, for example, there is another bundler called U2. U-T So U2 is another framework slash bundler that's built on top of the ToroPack core. And they're using it very successfully. They're conserving back to ToroPack as well because they're using it in this way. And they're building pretty large applications on this. So there's no reason that you wouldn't be able to use this as a standalone vendor in the same way that you would use ES build or feed or anything like that. It's on us to add a public API for this. And really, the only reason we haven't yet is we want to be really intentional about it. ([TimeĀ 0:49:50](https://share.snipd.com/snip/e8d901a2-7699-4516-87ad-0c7943bca1c9))