-
Notifications
You must be signed in to change notification settings - Fork 31
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
Astro middleware #531
Comments
If the middleware returned a response with a non-html body on SSG, should it ignore the path or throw an error? I think currently Astro throws an error if you try to build a page that returns a redirect response? |
I am collecting information on different fronts around Middleware. I want to ask you about a couple of things since you are one of the people who made the proposal.
|
Good point, for SSG (and SSR), one use can be post-processing the response html. Since this proposal also includes
I'm not sure where you got this from? By "Route specific middleware," I mean middleware that only runs on specific routes (like |
Thank you! Specifically, if you were a user, how would you use
Thank you for clarifying! |
@ematipico Another thing to think about, Vercel has a way to specific certain routes that run at the edge. Could/would we want our middleware layer to run at the edge if using the Vercel adapter? https://vercel.com/docs/build-output-api/v3#features/edge-middleware |
@matthewp |
I can't think of any specific examples right now for SSG, though I'm sure there are times where you want to run the same function between routes. I just think it doesn't make sense to limit middleware to SSR and have more SSR specific features. |
A prototype is available here: npm i [email protected] The prototype mostly follows the RFC, a small deviation:
export default defineConfig({
middlewareOrder: ['auth', 'server']
})
|
Probably okay to leave as-is given the good convo already kicked off, but for future proposals: we discussed reducing the effort on Stage 1/2 and moving away from including a specific solution in the OP to just including background/goals/non-goals. Solution ideas and discussion can go in a comment below (optional) at anytime, or just the RFC. Two examples: |
@ematipico any reason for the deviations you made? It would be helpful to evaluate them. Without context, I find the RFC API much more friendly vs. having to couple function names to a special string array in But, you also mention the |
@matthewp on your proposal: you mention
Big +1 for this. It's not just Vercel. Cloudflare is another example where they have the ability to automatically choose the best location for your worker to run in. Splitting middleware into its own cloud function would let you run the middleware It might be good to explicitly say that implementing either of these is out of scope, but that handling this future use-case in the API design now (ex: allowing someone else to implement it with this API) is in scope for this RFC, if at all possible. |
I'm not a big fan of using configuration to define middleware. It should be handled locally (within
Agree with @FredKSchott. No need to make it complicated.
I'd love to have this as a configurable option if |
The latest PR/preview release now has an identical implementation based on this RFC, with execption for the function called The |
@pilcrowonpaper what is the difference between exporting an array of middleware vs. using |
@matthewp That's a mistake! I think I accidentally included both. I prefer using export const middleware: Middleware = sequence(sequence()); I also think it makes more sense that there's only a single middleware function. Also, would a default export work better if the file name is const middleware: Middleware = async () => {
// ...
};
export default middleware; |
@pilcrowonpaper I was worried about nesting. My concern is that people will expect: const a = () => { ... };
const b = () => { ... };
const c = () => { ... };
const d = sequence(a, b);
export const middleware = sequence(b, c, d); Above |
@matthewp The expectation (or at least mine's) is that it'll run twice. |
could you show a snippet of how you set this up since your explanations are a little bit confusing |
I opened a PR: withastro/astro#6721 In the body of the issue I put some example. The npm tag to use is the following: Here's a link to the npm package: https://www.npmjs.com/package/astro/v/0.0.0-middleware-20230403121346
|
A few pieces of feedback after playing with this preview: We should expose Astro's declare module 'astro' {
interface Locals {
foo: string
}
} We should possibly rename We should possibly explore removing the requirement that Should we warn if a user tries to assign |
Here's some feedback of mine from trying out the APIs:
|
Should've included that in the RFC, but that was my intention. On renaming const middleware = async (context, next) => {
next()
} Though TypeScript should catch the error.
Ah yeah, agreed that we should make it so we don't have to pass on
@matthewp I'm kinda lost as to where this is pointing to |
To be more clear, the team felt kind worried about the possibility to override the For example, we could have two or more middleware where each one is in charge to change the locals. Then, a user, by mistake does this at the very end: context.locals = 155; This piece of code would make |
Ah, gotcha. I was kinda confused since I wrongly assumed setting |
@pilcrowonpaper Setting it to |
Some use-cases and how I would think to implement them: Redirect configexport const onRequest = ({ url, redirect }, next) => {
if(url.pathname.endsWith('.html')) {
return redirect(url.pathname.replace('.html', ''));
}
next();
} Dev-only routeexport const onRequest = ({ url }, next) => {
if(url.pathname === '/testing' && import.meta.env.MODE !== 'development') {
return new Response(null, {
status: 405
})
}
next();
}; Authenticationimport { sequence } from 'astro/middleware';
const auth = async ({ cookies, locals }, next) => {
if(!cookies.has('sid')) {
return new Response(null, {
status: 405 // Not allowed
});
}
let sessionId = cookies.get('sid');
let user = await getUserFromSession(sessionId);
if(!user) {
return new Response(null, {
status: 405 // Not allowed
});
}
locals.user = user;
next();
};
export {
auth as onRequest
} ---
const { user } = Astro.locals;
--- Protected routesimport { auth } from './auth';
const allowed = async({ locals, url }, next) => {
if(url.pathname === '/admin') {
if(locals.user.isAdmin) {
return next();
} else {
return new Response('Not allowed', {
status: 405
})
}
}
next();
};
export const onRequest = sequence(auth, allowed); Modifying response HTMLexport const onRequest = async (context, next) {
let response = await next();
if(response.headers.get('content-type') === 'text/html') {
let html = await response.text();
let minified = await minifyHTML(html);
return new Response(minified, {
status: 200,
headers: response.headers
});
}
return response;
} |
|
Not necessarily. It all depends of what a middleware wants to do.
The name is not final. I found the |
|
That's a good point. I would say yes, we should return what's returned by I don't mind the new suggested names :) |
Is there any thoughts on how Astro will expose This allows adapters to pass in a Is this in the same direction the RFC wanted to go? I'd like to help polish and upstream these changes, since having just this feature would be a huge win for us! BTW, the feature already reduced a lot of the work I needed to maintain before, so huge thanks for getting this in Astro already! |
@wrapperup I think there's a couple of ideas here:
Personally I like both (1) and (2) idea and am less sure about (3) as it messes with the ability for the app developer to control ordering. |
I like number 2, and that's essentially what we do now on our project. The nice thing is that the change is pretty dang small too, and it let us migrate all of our pages to Astro that required state from our Koa app. I have this work pretty much done in a fork, so I'd like to polish it up so that locals are passed in nicer and make a PR for it. I like 1 too, but in our case it wouldn't solve the above issue, since other parts of our app don't live in Astro (like our API). But both can exist! |
Is it possible to use middleware to add a Option 1Avoid it and redirect Option 2
Option 3
Option 4
A built-in middleware for properly handling A super hacky workaround I have is to build the plain output I have for all lang routes, Is it possible to be able to make fetch requests or generate different page responses in the middleware function? Using fetch for I think i18n should be a first-class concern for Astro. |
That is one of the use cases you can solve with middleware. Although, we are not there yet to provide a "native" solution to these problems using integrations. I would suggest shipping your solution for the time being! |
Is there a way to return a different page with the current middleware API? The
Do you have specific APIs you would recommend I look at? A push in the right direction would help a lot. |
@jlarmstrongiv Can't you use a redirect? The first argument of the middleware is |
@ematipico not really, no First, redirects don’t work when using SSG #466 (comment) but that can be worked around by returning an html meta redirect page Second, for the The workarounds are:
None of these are as good as supporting fetching page data from different routes in middleware so I can handle the |
Closing as this is completed and in stable. |
How can i do rewrite like in nextjs Middleware |
Does anyone have an example of how to use locals in a SSG page? |
Body
Astro.context
RFC RFC:Astro.context
#230Summary
Introduce a middleware to Astro, where you can define code that runs on every request. This API should work regardless of the rendering mode (SSG or SSR) or adapter used. Also introduces a simple way to share request-specific data between proposed middleware, API routes, and
.astro
routes.Background & Motivation
Middleware has been one of the most heavily requested feature in Astro. It's useful for handling common tasks like auth guards and setting cache headers. For me, it would make handling authentication much easier.
This proposal is heavily inspired by SvelteKit's
handle
hooks;Goals
Non-Goals
Example
A quick prototype
Middlewares can be defined in
src/middleware.ts
by exportingmiddleware
(array):A simple middleware looks like so:
context
is the same one provided to API route handlers. Most of Astro's request handling process will be behindresolve()
, and the response object returned frommiddleware
will be sent to the user's browser.Multiple middleware
sequence
can be imported to run multiple middlewares in sequence.The log result for this example is:
locals
This proposal also adds
locals
property toAPIContext
andAsroGlobal
. Thislocals
object will be forwarded across the request handling process, allowing for data to be shared between middlewares, API routes, and.astro
pages. This is useful for storing request specific data, such as user data, across the rendering step.The value type of
locals
can be anything as it won't be JSON-stringified:locals
can be typed insidesrc/env.d.ts
(I think it's possible?):SSG mode
The middleware function will run on each route's pre-rendering step, as if it were handling a normal SSR request.
The text was updated successfully, but these errors were encountered: