Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add server middleware support #13430

Closed
wants to merge 27 commits into from
Closed

Conversation

dummdidumm
Copy link
Member

@dummdidumm dummdidumm commented Feb 6, 2025

WIP

This adds support for server middleware to SvelteKit.

Middleware will run prior to all requests made to the server, including those to prerendered pages but excluding those to immutable assets. The current state of the API looks like this:

// hooks.middleware.js

export function middleware({ request, url, setRequestHeaders, setResponseHeaders, cookies, reroute }) {
	// add headers to the request object
	setRequestHeaders({ 'x-custom-req-header': 'custom-value' });
	// ...or add headers / cookies to the response object
	setResponseHeaders({ 'x-custom-res-header': 'custom-value' });
	cookies.set('custom-cookie', 'custom-value', { path: '/' });

	// bail completely and return a custom response
	if (url.pathname === '/middleware') {
		return new Response('Middleware response');
	}

	// do reroute from middleware
	if (url.pathname === '/prerendered') {
		return reroute('/foo');
	}

	// by not returning anything we continue the chain
}

Adapters have to opt in to supporting middleware, and you'll get an error at dev/build time when using it with an adapter that does not support it. Since middleware is very specific to deployment platforms and should be very fast, it deliberately only supports only a limited set of things you can do.

If your app is not using prerendered (or in case of Vercel ISR'd) pages, it probably does make more sense for you to express the desired logic within handle and reroute hooks.

TODOs:

  • implement adapters for various deployment platforms (Vercel/Netlify/cloudflare-pages/Node should be doable)
  • finalize API (we talked about possibly supporting redirect/error in here, and to align the API make reroute also throw
  • think about matcher API (run middleware on only a subset of pages) and possibly multiple middleware because of this
  • think about what should happen when you return a Response object from a route resolution request - right now the client will just error in the console. We either need to disallow that somehow, or turn the response into some kind of hard reload
  • think about how we want to handle rewrites/reroutes: A request to the route resolution path could lead to a wrong relative import within the JS response. Possible options:
    • Turn this into a JSON response, then we can always load it relative to the root JS script
    • Require (or automatically do it if possible) people to set paths.relative: false when they use middleware
    • Send a header to the SvelteKit runtime so that it knows the actual URL path
  • tests & docs

closes #10870


Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

Edits

  • Please ensure that 'Allow edits from maintainers' is checked. PRs without this option may be closed.

Copy link

changeset-bot bot commented Feb 6, 2025

⚠️ No Changeset found

Latest commit: af2eed3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@svelte-docs-bot
Copy link

@eltigerchino eltigerchino added the feature / enhancement New feature or request label Feb 7, 2025
@hanszoons
Copy link
Contributor

Could this somehow be leveraged to bypass CSRF protection for individual routes? See #6784

@ryu-man
Copy link

ryu-man commented Feb 7, 2025

Does the middleware run in both client and server? Or server only?

@dummdidumm
Copy link
Member Author

dummdidumm commented Feb 7, 2025

This is a server-only concept, clarified in the PR description

@dummdidumm dummdidumm changed the title feat: add middleware support feat: add server middleware support Feb 7, 2025
@Hugos68
Copy link

Hugos68 commented Feb 7, 2025

What's the difference between this new middleware API or using more specific hooks like handle or reroute? Will this PR replace those in the future?

@sacrosanctic
Copy link
Contributor

What's the difference between this new middleware API or using more specific hooks like handle or reroute? Will this PR replace those in the future?

This seems to include GET requests for routes that doesn't include a server load fn and the API is limited.

@elliott-with-the-longest-name-on-github
Copy link
Contributor

What's the difference between this new middleware API or using more specific hooks like handle or reroute? Will this PR replace those in the future?

This is a layer that allows us to take advantage of technology like Vercel's Edge Middleware, Netlify's Edge Functions, etc. to put a consistent layer of logic between the request and your app, no matter what kind of rendering you're using. Paired with the new serverside route resolution, this makes it possible to actually run something on every request to your app (including requests for static assets, prerendered routes, SSRed routes, and client-side navigations), not just those that hit the server.

Right now, if you wanted to run serverside logic prior to, say, retrieving a static asset, you could not -- meaning that if you wanted to generate two static pages, /home-a and /home-b, then rewrite /home to one of those two routes based on, say, a cookie... you just can't. Or if you wanted to set a cookie for each visitor on their first visit to your site, you can't do that when they visit a static page, because the handle hook won't be invoked.

Whether you can use this is platform-dependent -- the platform you're using has to actually provide a layer that can run before all requests to your app -- but enough platforms have mechanisms like this that we think it's probably a worthwhile abstraction.

@Antonio-Bennett
Copy link

(including requests for static assets, prerendered routes, SSRed routes, and client-side navigations), not just those that hit the server.

@elliott-with-the-longest-name-on-github the PR description says server requests? So just seeking explicit clarification that this will also run before client side navigation ofc I guess Simon would have to make that clarification if unknown by you.

Thank you for the great detailed response!

@elliott-with-the-longest-name-on-github
Copy link
Contributor

@Antonio-Bennett yes*

We recently created config.router.resolution: 'server' which causes routes to resolve on the server, meaning there's a server request for every navigation, which means middleware would run for client-side nav. If that feature isn't turned on though, it either wouldn't run or would run for the asset requests resulting from the navigation (depending on how we finalize the design) -- which could be useful for, say, injecting headers or cookies, but not as much for redirecting or rewriting.

);

static_config.routes.push({
src: '/.*', // TODO allow customization?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty much sure we have to ship matchers or some other way to customize this if we want any chance of ent customers using it

@@ -316,6 +316,53 @@ export const transport = {
};
```

## Middleware hooks

The following can be added to `src/hooks.middleware.js`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda feel like we should move to src/hooks/middleware.js so that in 3.0 we could potentially move to src/hooks/handle.js, src/hooks/reroute.js, etc. -- would make selectively deploying a hook to edge easier... 🤔


resolveId(id) {
if (
id.startsWith('$env/') ||

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

er, why? environment variables should be available 🤔

@dummdidumm
Copy link
Member Author

I know you just came here to talk shit, but

  • I invite you to read the docs on the handle hook, which already give you this functionality.
  • this PR provides adapter implementations for Netlify/Cloudflare/Node, too

@pawelblaszczyk5
Copy link
Contributor

pawelblaszczyk5 commented Feb 12, 2025

I invite you to read the docs on the handle hook, which already give you this functionality.

It doesn't.

Concrete use case: In the handle hook you can't catch exceptions thrown by the page loader (suppose you want to return a custom 404 response when the database layer throws a RecordNotFoundError or similar) because you have no access to the error instance. In the handleError hook you have access to the error instance but you cannot return a custom response.

That's a very basic use case and, AFAIK, it cannot be done with any of the current hooks, and this "middleware" doesn't seem to allow that either.

This should probably be called "edge handlers" or something like that. If you every tried any MVC framework (where I believe the common understanding of "middleware" comes from) you will see this is very far from a "proper middleware"

But you can implement middleware support however you want on adapter level. It can run on the edge, it can run wherever you want. Also worth pointing out - Vercel already doesn’t run middleware in the edge environment and they use their new “fluid” stuff.

@dummdidumm
Copy link
Member Author

dummdidumm commented Feb 12, 2025

Concrete use case: In the handle hook you can't catch exceptions thrown by the page loader

Could you elaborate further? Assuming you would have a way to know "an error was thrown" within handle, what would you do? return new Response('custom stuff in here')? Why wouldn't you implement that logic within a +error.svelte?

This should probably be called "edge handlers" or something like that

It indeed seems like the term "middleware" sparks wildly different thoughts on what this actually is - it's a very overloaded term. We're already thinking about whether or not there's a better term.

@dummdidumm
Copy link
Member Author

After exploring this we concluded that it's better to leverage the unique capabilities of each deployment provider's platform instead of providing the lowest common denominator.

  • For Vercel, that means supporting middleware.js
  • For Netlify, that means providing a way to deploy an edge middleware
  • For Cloudflare-Workers, that likely means getting this PR in
  • For Cloudflare-Pages, that likely means providing something similar to the workers PR, i.e. a way that you can hook into the worker
  • For Node, that means providing a way to add middleware via the (req,res,next) signature

@dummdidumm dummdidumm closed this Feb 13, 2025
@dummdidumm dummdidumm deleted the sveltekit-middleware branch February 13, 2025 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature / enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

A way to reverse proxying (like Next.js middlewares)
9 participants