Astro 6 logo

Astro 6

0

Astro 6 (Server Islands, Content Layer, Actions, Sessions, astro:env) Cursor plugin: 48 anti-patterns, 10 rules, 5 skills, reviewer agent, correct + anti-pattern fixtures, validator. Catches Astro 4/5 leftovers that current LLMs still produce.

10 rules

Add to Cursor
# Astro Actions and Forms Target: `astro ^6.3.3`. Actions stabilised in Astro 5.0 and replace the ad-hoc API endpoint + manual validation pattern. ## Where actions live Actions live under `src/actions/`. A single `src/actions/index.ts` exports a `server` namespace; Astro picks them up automatically. ```ts // src/actions/index.ts import { defineAction } from 'astro:actions' import { z } from 'astro:schema' export const server = { subscribe: defineAction({ input: z.object({ email: z.string().email(), }), handler: async ({ email }, ctx) => { await db.subscribers.insert({ email }) return { ok: true } }, }), uploadAvatar: defineAction({ accept: 'form', input: z.object({ avatar: z.instanceof(File), userId: z.string(), }), handler: async ({ avatar, userId }) => { const bytes = await avatar.arrayBuffer() await storage.put(`avatars/${userId}`, bytes) return { ok: true } }, }), } ``` Source: https://docs.astro.build/en/guides/actions/ ## Always import zod from astro:schema `astro:schema` re-exports the Zod instance that Astro bundles. Importing from your own `zod` dependency can produce two parallel Zod runtimes whose `instanceof` checks disagree (anti-pattern #46). ```ts // CORRECT import { z } from 'astro:schema' // WRONG inside src/actions/ import { z } from 'zod' ``` ## Calling actions ### From a .astro frontmatter ```astro --- import { actions } from 'astro:actions' const { data, error } = await Astro.callAction(actions.subscribe, { email: 'a@b.co' }) --- {error && <p class="text-red-500">{error.message}</p>} {data && <p>Subscribed.</p>} ``` `Astro.callAction` is the canonical server-side caller: it runs validation, normalises the return to `{ data, error }`, and is type-safe. Source: https://docs.astro.build/en/reference/api-reference/#callaction ### From a client component ```tsx import { actions } from 'astro:actions' async function onSubmit(e: SubmitEvent) { e.preventDefault() const { data, error } = await actions.subscribe({ email: 'a@b.co' }) if (error) showToast(error.message) } ``` ### From an HTML form (progressive enhancement) ```astro --- import { actions } from 'astro:actions' --- <form method="POST" action={actions.subscribe}> <input name="email" type="email" required /> <button type="submit">Subscribe</button> </form> ``` For file uploads, the `<form>` must declare `enctype="multipart/form-data"` (anti-pattern #31): ```astro <form method="POST" enctype="multipart/form-data" action={actions.uploadAvatar}> <input type="file" name="avatar" required /> <input type="hidden" name="userId" value={user.id} /> <button type="submit">Upload</button> </form> ``` ### Reading action results on a static page When a static page (no `export const prerender = false`) hosts a form that posts to an action, Astro performs the action then re-requests the page with a result attached to the request. Read it with `Astro.getActionResult`, NOT `Astro.callAction` (which is a per-request server caller that cannot run during a static prerender): ```astro --- import { actions } from 'astro:actions' const result = Astro.getActionResult(actions.subscribe) --- <form method="POST" action={actions.subscribe}> <input name="email" type="email" required /> <button type="submit">Subscribe</button> </form> {result && !result.error && <p>Thanks, you are subscribed.</p>} {result?.error && <p class="error">{result.error.message}</p>} ``` Use `Astro.callAction(actions.x, input)` only when the host page is on-demand-rendered (`export const prerender = false`) or in `output: 'server'`. Source: https://docs.astro.build/en/reference/api-reference/#astrogetactionresult ## Returning errors Throw an `ActionError` with the proper code: ```ts import { defineAction, ActionError } from 'astro:actions' import { z } from 'astro:schema' export const server = { login: defineAction({ input: z.object({ email: z.string().email(), password: z.string() }), handler: async ({ email, password }) => { const user = await db.users.byEmail(email) if (!user || !user.checkPassword(password)) { throw new ActionError({ code: 'UNAUTHORIZED', message: 'Bad credentials' }) } return { userId: user.id } }, }), } ``` Source: https://docs.astro.build/en/reference/modules/astro-actions/ ## Common mistakes - No `input` schema: handler receives `unknown` (anti-pattern #3). - Importing `z` from `zod` instead of `astro:schema` (anti-pattern #46). - File form without `enctype="multipart/form-data"` (anti-pattern #31). - Calling `actions.subscribe(input)` directly from frontmatter instead of `Astro.callAction(actions.subscribe, input)` (anti-pattern #30). - Action named `apply` outside `src/actions/index.ts` (anti-pattern #44).
Add to Cursor
Add to Cursor
Add to Cursor
Add to Cursor
Add to Cursor
Add to Cursor
Add to Cursor
Add to Cursor