01
Start with native HTML
A Form preserves regular browser submission. SubmitButton adds an enhancement boundary but a basic post must remain understandable and useful without client JavaScript.
02
Treat Actions as public endpoints
Validate input on the server, authorize the request, set a trusted Origin policy, bound request bodies, and return safe error messages.
03
Plan for retries
Use idempotency keys where retrying can duplicate a mutation. Keep replay storage bounded and choose cookie attributes and retention policy deliberately.
PRACTICAL LABS
Run this capability.
Each example states the observable output, the boundary that remains your responsibility, and the check that proves the result.
01
Keep the native form
TSX<form action="/api/support" method="post">
<label htmlFor="message">Message</label>
<textarea id="message" name="message" minLength={20} required />
<button type="submit">Send</button>
</form>- OUTPUT
- The browser can submit the support request without JavaScript.
- BOUNDARY
- Browser validation is usability only; validation and authorization belong on the server.
- PROVE IT
- Submit once with JavaScript disabled and assert a safe server response.
02
Validate an Action on the server
TypeScriptimport { action } from '@mohammedaydan/actions'
const support = action({
endpoint: '/api/support',
validate: parseSupportInput,
authorize: requireSupportAccess,
handle: saveSupportRequest,
})- OUTPUT
- The handler receives validated, authorized input only.
- BOUNDARY
- Use durable idempotency storage for retriable multi-instance mutations.
- PROVE IT
- Test invalid data, bad Origin, denied access, and a duplicate key.
VERIFICATION
Prove the contract.
Test native submission, enhanced submission, invalid input, duplicate requests, CSRF/origin rejection, and authorization failures.