RFC: Paving a path from 𝑂(n·m) to 𝑂(n) Vite deployment target plugins #20907
Replies: 12 comments 18 replies
-
|
I know this is a bit of a meandering, fuzzy RFC. My hope is to use this as a starting point and evolve it as folks chime in and collaborate. 👋🏼 FYI I'll be at ViteConf this week, including all the evening events. Happy to discuss this with anyone! |
Beta Was this translation helpful? Give feedback.
-
|
Hi, @serhalp 👋 I definitely want to reduce the complexity from 𝑂(n·m) to 𝑂(n), and that's one of the subgoals of the Env API. The original idea was that a runtime provider would offer an Environment Factory, which the framework would then consume. What I personally find most challenging is defining the right separation of responsibilities among Vite, meta-frameworks, and runtime providers. Ideally, we'd maximize the capabilities of both meta-frameworks and runtime providers while minimizing framework/runtime-specific logic — but we don't yet know what kind of interface could satisfy that. The lack of established conventions also makes things harder. Moreover, I feel it's difficult for me to propose conventions between meta-frameworks and runtime providers, since I don't have a deep enough meta-framework perspective. In the past, there was a discussion about who should "control" the build (#16358 (reply in thread)). I guess solving Needs 3 and Needs 4 may help this problem. |
Beta Was this translation helpful? Give feedback.
-
|
Regarding Needs 1 / Solution 1: If there's only one entry point, I think specifying it via
If Needs 2 were resolved, we could simply specify |
Beta Was this translation helpful? Give feedback.
-
|
Regarding Solution 2: I think it would be helpful to have some form of specification (even if loosely defined), but I don't think this is something that the Vite team can or should propose directly. It would be helpful if not only the route but also the middleware is some standardized as that would help migrating the internal middlewares. |
Beta Was this translation helpful? Give feedback.
-
|
Regarding Solution 3: I understand the necessity of Needs 3, but I think it's difficult to design an interface that doesn't limit the meta-framework's capabilities. I guess each meta-frameworks would have to know each runtimes capabilities due to the differences, and maybe Vite should focus on how the meta-frameworks and the runtime provider plugins can communicate. |
Beta Was this translation helpful? Give feedback.
-
|
Thank you, @serhalp, for sparking this discussion. It's great to see both Netlify and Cloudflare showing interest in this. The only reason we created Photon was that we didn't see any alternatives, and it hadn't occurred to us to create such an RFC. Let me suggest actionables, both short- and long-term. [Short term] Agnostic Netlify/Cloudflare pluginsGoal: Make deploying to Netlify or Cloudflare as simple as adding // vite.config.js
import cloudflare from '@cloudflare/vite-plugin'
// Works with TanStack, Nitro, Photon, ...
export default {
plugins: [
cloudflare()
]
}In other words:
This is basically the convention we'd need to agree on. I briefly spoke with Joël about it (@magne4000, Photon lead), and for @pi0 Is that something Nitro would be interested in as well? WDYT? 👀 From our side, we'd be totally up for starting to work on this, with the goal of fully replacing Same for One neat thing here is that I don't think we need to involve the Vite team at all. It's just a convention between Netlify/Cloudflare and TanStack/Nitro/Photon (and eventually everyone). [Short term] Universal middlewares/handlersGoal: Define request middlewares/handlers that work anywhere. We started Most notably:
We actually already started discussing this (Pooya, Joël, and myself) after meeting at ViteConf. I think the three of us can come up with a good convention. [Short term] vite-plugin-vercelGoal: Bring Vercel on board. Joël is the maintainer of vite-plugin-vercel. We will be reaching out to Vercel to see if they are interested (ideally making it an official Vercel plugin [Future] Vite Deployment APIGoal: Remove the need for Nitro/Photon as much as possible. I ain't exactly sure how this would look. It will probably become much clearer after we start working on the short-term actionables above. Idea: A new Vite CLI command [Proposal] NPM package naming?How about we rename the Vite plugins? - @netlify/vite-plugin
+ @netlify/vite
- @cloudflare/vite-plugin
+ @cloudflare/viteFor me personally, it was a paper cut when I started using it — I kept forgetting whether it's It's a low-hanging fruit, IMO. |
Beta Was this translation helpful? Give feedback.
-
|
Thanks for starting this conversation, @serhalp. We're definitely thinking about this at Cloudflare as well. As the scope of Vite grows, with the Environment API and now Vite+, this seems like the logical next step. Here are some initial thoughts: Firstly, I think we need to be thinking about this as more directly related to the Environment API. One implication of this is that anything that applies at build time should be reflected as closely as possible in dev. This should apply to any new conventions around specifying server entries, routing manifests etc.. An option here that reflects how we currently implement things in I also think we could work towards more concretely defining what an environment is and use this as a concept to build upon. Would it be too restrictive for each server environment to have a single server entry? If not, then that seems a good way to establish the contract here. The routing manifest would then be routing requests to environments. It would be a platform concern how these environments then map to deployable units (serverless functions etc.). The server bundles feature in React Router already has a separate environment for each bundle and Given the above, we may be able to solve 1 (server entry point location) by having a way to specify the entry for server environments. Perhaps we could even reuse the existing build.ssr option for this as it already accepts a string. The platform plugin could then use this when determining how to populate Regarding 2 (server entry point signature), we would be in favour of standardising on the fetchable convention with the default export but that's probably not a surprise. Having a standard here would definitely simplify things but I'd be interested to hear any counter arguments to this part of the proposal. 3 (routing metadata) is where most of the complexity lies. Trying to build on top of web standards may help focus things. As I've already mentioned, I think it would be worth exploring the idea of routing to environments and each environment having a single server entry. The key here is to come up with something that doesn't stifle innovation, either on the framework or platform side. Part of me thinks that this could be quite simple and patterns could just use wildcards, similar to Cloudflare's route matching. Input from framework authors is really important here though. As you've highlighted, one advantage of formalising this routing information is that platforms can implement the routing on behalf of users and avoid charging for 404s. I think that prerendering should also be considered as part of this proposal as it's become a common feature across frameworks. I've also been wondering recently if this should be associated more directly with the environment concept. There will likely always be some cases where frameworks want to abstract over platform features e.g. image optimisation, caching etc.. Even if some frameworks continue to use adapters then wrapping platform plugins in these adapters would still reduce the maintenance burden when compared to not having platform plugins at all. Finally, I want to add that Nitro and Photon don't necessarily need to be seen as counter to this proposal. |
Beta Was this translation helpful? Give feedback.
-
1) Server entry point locationWe need a single, predictable place to declare server entries and attach their metadata (routing, runtime, etc.). Centralizing this information makes it easier for frameworks and providers to read and act on it. Several options have been discussed across this RFC:
Regardless of the final shape, we must be able to attach metadata to each server entry (which environment builds it, which routes point to it, etc.). As an incremental step, using the Plugin // @vitejs-community/this-rfc
export const FETCHABLE_SERVER_ENTRIES_KEY = '__FETCHABLE_SERVER_ENTRIES__'
export interface FetchableServerEntry {
id: string; // Module ID resolvable by Vite that points to the server entry
name: string // Human-readable label for debugging and user-facing messages
env?: string; // Examples: 'ssr', 'rsc', 'edge' (details below)
runtime: 'node' | 'edge'; // Generic hint to help providers choose the build environment
}
// Keying entries helps resolve virtual modules and enables quick cross-references
export interface FetchableServerEntries extends Record<string, FetchableServerEntry> {}// @tanstack/start-plugin-core
import { FETCHABLE_SERVER_ENTRIES_KEY, type FetchableServerEntry } from "@vitejs-community/this-rfc"
export default {
/* ... */
configResolved(config) {
this.api[FETCHABLE_SERVER_ENTRIES_KEY] = {
// This entry can be referenced via `virtual:fetchable-server-entry:entryKey`
entryKey: {
/* ... */
},
/* ... */
} satisfies FetchableServerEntries
}
}
How do we choose which env builds which entry?Each provider should decide which Vite environment builds a given entry, with a sensible fallback to a standard Node runtime. That fallback can be provided by a shared plugin like An entry may explicitly set If |
Beta Was this translation helpful? Give feedback.
-
3) Routing metadata
Practically, we don’t need to hard‑enforce a "one server entry per environment" rule. If multiple routes end up targeting two different entries within the same environment, Frameworks that declare server entries should declare routing alongside them: // @vitejs-community/this-rfc
export interface FetchableServerEntry {
/* ... */
route: URLPattern | URLPattern[]; // Routes that should be handled by this entry
method: string | string[]; // e.g. 'GET' or ['GET', 'POST']
// Keep room for experimentation
// Additional, provider-specific metadata for non-generic or experimental features
cloudflare?: { /* ... */ };
vercel?: {
isr?: number, // Vercel ISR configuration (seconds)
/* ... */
};
} |
Beta Was this translation helpful? Give feedback.
-
Thoughts around Observability (OpenTelemetry)Currently, OpenTelemetry relies on The exact problem statement is described here: https://github.com/getsentry/esm-observability-guide/tree/main?tab=readme-ov-file#problem-statement Having to rely on the CLI I think this proposal for Vite enables an amazing opportunity to solve ecosystem issues around Observability: Instead of each each framework having to implement a wrapped server-entry (like SvelteKit already does), Vite could build and expose the server entry in a way that allows running some code before the application starts. It would depend on the server output, but in general this could look like: import './instrumentation.mjs';
// dynamically imported, so the instrumentation can be preloaded
import('./server.mjs'); // file which contains code from previous index.mjs server-entry fileAlso read the guide for Meta-Frameworks here: https://github.com/getsentry/esm-observability-guide/blob/main/guides/meta-framework-authors.md |
Beta Was this translation helpful? Give feedback.
-
|
@serhalp Maybe creating an RFC repository could be convenient — for a single place for all discussions, organized on a topic-by-topic basis (using Name proposal: |
Beta Was this translation helpful? Give feedback.
-
|
To play devil's advocate a little here, what does this proposal achieve that can't be achieved by a framework wrapping a platform plugin with a platform specific adapter and configuring it that way? I feel that answering this question might help focus things a little. The risk with building this into Vite itself is that it becomes an ever expanding surface area that has to satisfy the requirements of all frameworks and platforms. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
TL;DR
Each web deployment provider currently requires a unique, ad hoc integration layer for each meta-framework it intends to support.
Does it have to be this way? Could Vite be the bridge, allowing for a single plugin per provider to just work?
This document discusses how we got here, solutions attempted in the past, what’s changed since, the few remaining technical challenges to solve, and starts riffing on some potential solutions.
Solving this would benefit developers by offering more deployment options for their favourite frameworks and more framework options for their favourite deployment platforms. Framework maintainers and deployment providers would also benefit from reduced maintenance and landing near-universal support at once when they land changes.
Context
The N×M Problem
Currently, each framework requires an adapter for each deployment platform, as illustrated by this spaghetti:
Supporting 10 Vite-powered frameworks on Netlify requires 10 adapters. Each provider requires its own 10 adapters. Here, we see 10x4=40 adapters for 4 providers.
Each provider currently maintains—in collaboration with the framework maintainers and the OSS community—largely duplicative adapters for Astro, Remix, React Router, Hydrogen, SvelteKit, Nuxt, TanStack Start, SolidStart, Analog, Qwik, and more.
This maintenance burden comes at an opportunity cost for providers and framework maintainers, who could focus those efforts on shipping end-user-facing improvements instead.
This also has a tendency to leave smaller frameworks behind. Many adapters are under-maintained and, in reality, most of the 40 adapters in the diagram above simply do not exist.
This fragmentation may have been inevitable just a few years ago, but the ecosystem has since rapidly rallied around Vite as bundler and dev server: all ten of the frameworks Iisted above use Vite—and the dominoes continue to fall. This, coupled with the opportunities unlocked by the Vite Environment API, leads to this question:
Does it still have to be this way?
Given Vite as unifying dev server, front-end meta-framework toolkit, client bundler, and server bundler (and much more…), might it also act as the unifying bridge that would decouple providers from full-stack frameworks (and vice versa)?
Could each deployment provider maintain a single, agnostic Vite plugin?
Supporting 10 Vite-powered frameworks on Netlify should require one single agnostic Vite adapter. Each provider should require its own single adapter. Here, we see 1x4=4 adapters instead of 40 previously.
I’m vaguely aware that Cloudflare has started working on this (or something like it) and that they have an initial version working in some capacity with React Router 7 and TanStack Start. We (Netlify) have started discussing this with @tannerlinsley and other framework maintainers, and we recently soft-launched a TanStack Start Vite plugin that doesn’t actually contain any TanStack Start-specific code. I’m creating this RFC as an effort to consolidate our efforts across the ecosystem and collaborate together on solving identified challenges at their root wherever possible.
What does it take to deploy a full-stack web app?
Let’s step back.
Fully static sites (SSG) are easy: the provider just needs to know what directory to publish as static files.
When frameworks allow first-class configuration of static redirects and response headers, these must be translated into a provider-specific configuration files at build time.
Once servers are involved (SSR, API routes, RSCs, etc.), it gets hairier.
The provider must:
That about covers it. (To keep this focused, I’ll ignore local development—which is the main focus of the ongoing Vite Environment API work—and focus only on building for production.)
Scope
This can get quite muddled, so I’m going to propose some guidelines as a starting point:
If you disagree with these, stop reading here and let’s discuss this first!
Needs
Through some research, discussions, and experimentation, we’ve identified some challenges to making this a reality today. I’ll discuss each separately.
1. Server entry point location
In order to generate the necessary deployment configuration, a provider plugin must know where to find the file that contains the framework’s request handler (in this document I’ll refer to this file as the server entry point).
(See also need 3.1 below for a further complexity regarding multiple server entry points.)
In the current n·m world, a framework-specific, provider-specific adapter either has programmatic access via its framework-specific adapter interface (for example, see Astro), or it has documented knowledge of this from the framework.
In some cases I’ve seen,
config.build.rollupOptions.inputduring thessrbuild (or the onebundleelement withtype === "chunk" && isEntry) is assumed to be that file. This leaky assumption that a bundle entry happens to be a server entry point may hold for some frameworks in some cases, but not all.rollupOptions.inputcan reference multiple bundle entries, some or all of which may not be server entry points. In theory, a server entry point could even not be configured as a bundle entry at all.It also isn’t clear how this would work given multiple
consumer === "server"Vite Environments (e.g. SSR and RSC).2. Server entry point signature
In order to generate the necessary deployment configuration, a provider plugin must know how to invoke the framework’s request handler and understands the semantics of its responses.
In the current n·m world, a framework-specific, provider-specific adapter has documented knowledge of this from the framework (for example, see React Router 7).
3. Routing metadata
Information about user-configured routes is needed at deployment time for several reasons:
rscserver entry point and none to thessrserver entry point directly, but there is no available mechanism for an agnostic Vite provider plugin to know this.In the current n·m world, a framework-specific, provider-specific adapter either has programmatic access to routing metadata via its framework-specific adapter interface (for example, see Astro), or it hardcodes assumptions that there is a single server entry point and that all un-shadowed requests can be routed to it.
4. Static redirects and headers
Some frameworks support configuration of static redirects and static response headers (for example, see Nuxt). Providers must translate these into provider-specific configuration at deployment time.
In the current n·m world, a framework-specific, provider-specific adapter has programmatic access to this configuration via its framework-specific adapter interface.
Proposal
Solution
1. Server entry point location
I saw the proposed(?)
environment.config.entryPointburied in this other RFC. That seems like a step in this direction, but it still assumes a single server entry point per environment.Perhaps output entry chunks could have an
isServerEntry: booleanproperty?See solution to need 3 below, which may cover need 1 as well.
Note that, as of very recently, TanStack Start and Cloudflare are experimenting with a conventional
server-entryexport; but it isn’t clear how this would help with a metaframework-agnostic provider plugin. This expects the end user to hook up the entry point, or for this to be done in provider templates, neither of which is (IMO) ideal DX.2. Server entry point signature
A signature inspired by the Service Worker Web API has been gaining steam across similar realms: among many others, Cloudflare Workers, Bun, Deno, srvx, and now Vercel support this module interface:
As far as I can tell, this is not documented as a standard and the community has not yet even rallied around a single name for this; we’ve sort of aligned on “fetch handler” as a name for the handler function, but nothing yet for the above.
Much like Thenables are minimally defined by simply having a
.thenmethod of a certain type, I propose we name this aFetchable, defined as a module having a default export that is an object having a propertyfetchthat is a function accepting a fetchRequestand returning a fetchResponseor a promise of one; additional named exports are allowed, as are additional properties on the default export object.I’m not quite sure what I’m proposing concretely here, since Vite core wouldn’t really know about this at all. Perhaps this convention could be documented in the Vite SSR docs? Or perhaps output chunks could have an
isFetchableEntry: boolean? Or maybe Environment instances could havesupportsFetchableEntry: boolean?serverEntryType?: undefined | 'fetchable'?I found some highly relevant past comments but in a dev context:
3. Routing metadata
Important
Update (2025-12-04): I've written up a concrete, self-contained RFC for this here: #21212.
The goal is for a deployment provider’s Vite plugin to have some sort of access to route information at build time in
environment.consumer === 'server'environments, handed off by metaframeworks somehow. This simplified pseudocode illustrates the general idea:The format used to describe routes and route matching conditions would need to cover the union of all possible requirements across SSR-enabled metaframeworks.
I’m not aware of an existing standard interchange format for this, but it could possibly build upon the URL Pattern API and perhaps the proposed URLPatternList. (I know the Remix folks are also working on something possibly relevant?) It might be interesting to pursue drafting a standard, but this isn’t necessary.
💡 It’s possible some solutions here may also serve as a sufficient solution to need 1 above: the route manifest could serve as the source of truth for server entry points and their paths, and an omitted server bundle entry would indicate that it is not a server entry point. Similarly, it may serve as a solution to need 4: redirects may easily be argued to be routing information, although response headers not so much.
4. Static redirects and headers
There are arguments to be made to consider this need in scope or out of scope for Vite. I propose that these remain out of scope; although as called out above, it may be reasonable to include redirects in routing metadata and consider only headers out of scope.
Frameworks that want to support to support this will have a few options:
_redirectsand_headersformats)Alternatives considered
Nitro
Nitro was extracted from Nuxt as an attempt to solve roughly the same problem as this RFC.
It was started at a different time—before the rise to near-ubiquity of Vite and before the Vite Environment API was dreamt up. Its architecture requires that metaframeworks adopt it as their server engine layer, which no existing frameworks have done so far.
Although Nitro v3 will become a Vite plugin based on the Vite Environment API, installed directly by end developers, challenges remain:
Leveraging Vite as the foundation instead is, at the very least, worth exploring.
Vike
Vike (formerly
vite-plugin-ssr) and Vike Server are also worth calling out. I’m not deeply familiar with these efforts, but as far as I understand it provides an alternative to metaframeworks, rather than a solution to decoupling their preparation for deployment from deployment providers. I believe it also has ambitions similar to Nitro, with a similar approach requiring metaframeworks to adopt it, none of which have.The same team has also started working on extracting this layer from Vike as Photon and, further below that, https://universal-middleware.dev/. Peek at the
universal-middlewaresource and you’ll see it’s consolidating (part of) the 𝑂(n·m) complexity into one codebase, not simplifying it.Don’t do any of this
We could do none of this. There’s certainly an argument to be made that granular request routing information is not a concern that belongs in Vite core.
Providers could instead do one (or some combination of) the following:
Beta Was this translation helpful? Give feedback.
All reactions