What Causes This Error?
This error occurs when you call an action directly from server-side code (like the frontmatter of an Astro component). Actions are designed to be called from the client, with special methods for server-side form handling.
The Problem
---
// ❌ Calling action directly in frontmatter (server-side)
import { actions } from 'astro:actions';
const result = await actions.subscribe({ email: 'test@example.com' });
---
The Fix
Call from Client-Side
---
export const prerender = false;
---
<form id="subscribe-form">
<input type="email" name="email" />
<button type="submit">Subscribe</button>
</form>
<script>
// ✅ Call from client-side JavaScript
import { actions } from 'astro:actions';
document.getElementById('subscribe-form')
?.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const result = await actions.subscribe({
email: formData.get('email'),
});
});
</script>
Common Scenarios
Form Submissions (No JavaScript)
---
// For server-side form handling, use getActionResult
import { actions } from 'astro:actions';
export const prerender = false;
// ✅ Get result from form submission
const result = Astro.getActionResult(actions.subscribe);
---
<!-- Use form action attribute -->
<form method="POST" action={actions.subscribe}>
<input type="email" name="email" required />
<button type="submit">Subscribe</button>
</form>
{result?.error && <p class="error">{result.error.message}</p>}
{result?.data && <p class="success">Subscribed!</p>}
Client Component Calling Actions
// src/components/SubscribeForm.tsx (React)
import { actions } from 'astro:actions';
export default function SubscribeForm() {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
// ✅ Client-side call
const result = await actions.subscribe({
email: formData.get('email') as string,
});
if (result.data) {
alert('Subscribed!');
}
};
return (
<form onSubmit={handleSubmit}>
<input type="email" name="email" required />
<button type="submit">Subscribe</button>
</form>
);
}
Direct Server-Side Logic
---
// If you need server-side logic, use the handler directly
export const prerender = false;
// ❌ Don't call action
// const result = await actions.subscribe({ email });
// ✅ Use the same logic in an API endpoint or directly
async function subscribeUser(email: string) {
// Same logic as your action handler
await db.insert({ email });
return { success: true };
}
// Use in form handling
if (Astro.request.method === 'POST') {
const formData = await Astro.request.formData();
const email = formData.get('email') as string;
const result = await subscribeUser(email);
}
---
API Endpoint Alternative
// src/pages/api/subscribe.ts
import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request }) => {
const data = await request.json();
// ✅ Server-side logic in API endpoint
await db.insert({ email: data.email });
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json' },
});
};
Using getActionResult
---
import { actions } from 'astro:actions';
export const prerender = false;
// ✅ getActionResult for form submissions
const subscribeResult = Astro.getActionResult(actions.subscribe);
const isSubscribed = subscribeResult?.data?.success;
---
<form method="POST" action={actions.subscribe}>
{!isSubscribed ? (
<>
<input type="email" name="email" required />
<button type="submit">Subscribe</button>
</>
) : (
<p>Thanks for subscribing!</p>
)}
</form>
Progressive Enhancement
---
import { actions } from 'astro:actions';
export const prerender = false;
const result = Astro.getActionResult(actions.subscribe);
---
<!-- Works without JavaScript (form submission) -->
<form id="subscribe-form" method="POST" action={actions.subscribe}>
<input type="email" name="email" required />
<button type="submit">Subscribe</button>
</form>
{result?.data && <p>Subscribed!</p>}
<!-- Enhanced with JavaScript -->
<script>
import { actions } from 'astro:actions';
document.getElementById('subscribe-form')
?.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
// JavaScript takes over for better UX
const result = await actions.subscribe({
email: formData.get('email'),
});
if (result.data) {
// Show success message without page reload
}
});
</script>
When to Use What
Client-side (actions.myAction):
- Interactive forms
- Real-time updates
- Better UX (no page reload)
- React/Vue/Svelte components
Server-side (getActionResult):
- Progressive enhancement
- Works without JavaScript
- Form submissions
- SEO-friendly forms
Quick Checklist
- Don’t call actions in Astro frontmatter
- Use
<script>tag for client-side calls - Use
getActionResultfor server-side form handling - Use form
actionattribute for progressive enhancement - Set
prerender = falsefor action pages