Skip to content

Commit

Permalink
[Actions] Fix middleware warning static mode (#11794)
Browse files Browse the repository at this point in the history
* fix: remove static usage warning with isPrendered flag

* fix(test): cookie is empty for prerendered routes in dev

* chore: add test route

* chore: changeset
  • Loading branch information
bholmesdev authored Aug 20, 2024
1 parent 7e2f142 commit 3691a62
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/chatty-cups-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes unexpected warning log when using Actions on "hybrid" rendered projects.
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ export async function getStaticPaths() {
}));
}
type Props = CollectionEntry<'blog'>;
const post = await getEntry('blog', Astro.params.slug)!;
const { Content } = await post.render();
if (Astro.url.searchParams.has('like')) {
await Astro.callAction(actions.blog.like.orThrow, {postId: post.id});
await Astro.callAction(actions.blog.like.orThrow, { postId: post.id });
}
const comment = Astro.getActionResult(actions.blog.comment);
Expand Down Expand Up @@ -57,17 +56,17 @@ const commentPostIdOverride = Astro.url.searchParams.get('commentPostIdOverride'
/>
<form method="POST" data-testid="progressive-fallback" action={actions.blog.comment.queryString}>
<input type="hidden" name="postId" value={post.id} />
<label for="fallback-author">
Author
</label>
<input id="fallback-author" type="text" name="author" required />
<label for="fallback-body" class="sr-only">
Comment
</label>
<label for="fallback-author"> Author </label>
<input id="fallback-author" type="text" name="author" required />
<label for="fallback-body" class="sr-only"> Comment </label>
<textarea id="fallback-body" rows={10} name="body" required></textarea>
{isInputError(comment?.error) && comment.error.fields.body && (
<p class="error" data-error="body">{comment.error.fields.body.toString()}</p>
)}
{
isInputError(comment?.error) && comment.error.fields.body && (
<p class="error" data-error="body">
{comment.error.fields.body.toString()}
</p>
)
}
<button type="submit">Post Comment</button>
</form>
<div data-testid="server-comments">
Expand Down
28 changes: 16 additions & 12 deletions packages/astro/src/actions/runtime/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,18 @@ export type Locals = {
};

export const onRequest = defineMiddleware(async (context, next) => {
if ((context as any)._isPrerendered) {
if (context.request.method === 'POST') {
// eslint-disable-next-line no-console
console.warn(
yellow('[astro:actions]'),
'POST requests should not be sent to prerendered pages. If you\'re using Actions, disable prerendering with `export const prerender = "false".',
);
}
return next();
}

const locals = context.locals as Locals;
const { request } = context;
// Actions middleware may have run already after a path rewrite.
// See https://github.com/withastro/roadmap/blob/feat/reroute/proposals/0047-rerouting.md#ctxrewrite
// `_actionPayload` is the same for every page,
Expand All @@ -38,16 +48,6 @@ export const onRequest = defineMiddleware(async (context, next) => {
return renderResult({ context, next, ...actionPayload });
}

// Heuristic: If body is null, Astro might've reset this for prerendering.
if (import.meta.env.DEV && request.method === 'POST' && request.body === null) {
// eslint-disable-next-line no-console
console.warn(
yellow('[astro:actions]'),
'POST requests should not be sent to prerendered pages. If you\'re using Actions, disable prerendering with `export const prerender = "false".',
);
return next();
}

const actionName = context.url.searchParams.get(ACTION_QUERY_PARAMS.actionName);

if (context.request.method === 'POST' && actionName) {
Expand Down Expand Up @@ -92,7 +92,11 @@ async function handlePost({
context,
next,
actionName,
}: { context: APIContext; next: MiddlewareNext; actionName: string }) {
}: {
context: APIContext;
next: MiddlewareNext;
actionName: string;
}) {
const { request } = context;

const baseAction = await getAction(actionName);
Expand Down
11 changes: 9 additions & 2 deletions packages/astro/src/core/render-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ export class RenderContext {
const { cookies, middleware, pipeline } = this;
const { logger, serverLike, streaming } = pipeline;

const isPrerendered = !serverLike || this.routeData.prerender;

const props =
Object.keys(this.props).length > 0
? this.props
Expand All @@ -125,7 +127,7 @@ export class RenderContext {
logger,
serverLike,
});
const apiContext = this.createAPIContext(props);
const apiContext = this.createAPIContext(props, isPrerendered);

this.counter++;
if (this.counter === 4) {
Expand Down Expand Up @@ -212,12 +214,17 @@ export class RenderContext {
return response;
}

createAPIContext(props: APIContext['props']): APIContext {
createAPIContext(props: APIContext['props'], isPrerendered: boolean): APIContext {
const context = this.createActionAPIContext();
return Object.assign(context, {
props,
getActionResult: createGetActionResult(context.locals),
callAction: createCallAction(context),
// Used internally by Actions middleware.
// TODO: discuss exposing this information from APIContext.
// middleware runs on prerendered routes in the dev server,
// so this is useful information to have.
_isPrerendered: isPrerendered,
});
}

Expand Down
23 changes: 23 additions & 0 deletions packages/astro/test/actions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as cheerio from 'cheerio';
import * as devalue from 'devalue';
import testAdapter from './test-adapter.js';
import { loadFixture } from './test-utils.js';
import { serializeActionResult } from '../dist/actions/runtime/virtual/shared.js';

describe('Astro Actions', () => {
let fixture;
Expand All @@ -25,6 +26,28 @@ describe('Astro Actions', () => {
await devServer.stop();
});

it('Does not process middleware cookie for prerendered routes', async () => {
const cookie = new URLSearchParams();
cookie.append(
'_astroActionPayload',
JSON.stringify({
actionName: 'subscribe',
actionResult: serializeActionResult({
data: { channel: 'bholmesdev', subscribeButtonState: 'smashed' },
error: undefined,
}),
}),
);
const res = await fixture.fetch('/subscribe-prerendered', {
headers: {
Cookie: cookie.toString(),
},
});
const html = await res.text();
const $ = cheerio.load(html);
assert.equal($('body').text().trim(), 'No cookie found.');
});

it('Exposes subscribe action', async () => {
const res = await fixture.fetch('/_actions/subscribe', {
method: 'POST',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
import { actions } from 'astro:actions';
export const prerender = true;
const result = Astro.getActionResult(actions.subscribe);
---

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>{result?.data?.subscribeButtonState ?? 'No cookie found.'}</body>
</html>

0 comments on commit 3691a62

Please sign in to comment.