Docs menu

Getting started

IntroductionInstallationProject structure

Building pages

RoutingPagesLayoutsStylingMarkdownContent and dataIslands

Going live

SEO and headDeployingForms

Guides

Build a brochure site

Advanced

Running as a server

Reference

Reference

Advanced

Running as a server

For sites that handle their own form posts. Most sites don't need this.

Most sites are static and use a form service. This page is for the exceptions: sites that want to receive form posts themselves.

Handling posts

A page that exports post receives its form posts:

export def post {form}
	return {status: 422, values: form, errors: {email: 'Please enter your email.'}} unless form.email
	await sendEnquiry(form)                       # your own code: Resend, Postmark, …
	{redirect: '/contact/thanks'}                 # → 303 redirect

export default tag ContactPage
	form = null                                   # what `post` returned, when re-shown

	<self>
		<form method='post' action='/contact'>
			<input type='email' name='email' value=(form..values..email or '')>
			<small> form.errors.email if form..errors..email
			<button type='submit'> "Send"
  • form holds the posted fields as strings; repeated fields (checkboxes) arrive as an array.
  • Return {redirect} when it worked, or anything else to show the page again with it as the form prop. status sets the response code, e.g. 422 for validation errors.
  • Plain form posts and JSON are read, up to 1 MB. File uploads aren't supported yet (415). Pages without post answer posts with 405.
  • Keep required and type='email' in the HTML too: the browser checks first, the server checks again.

Sending the email is your own code. With Resend it's one fetch:

export def sendEnquiry form
	let res = await fetch('https://api.resend.com/emails',
		method: 'POST'
		headers: {'Authorization': "Bearer {process.env.RESEND_API_KEY}", 'Content-Type': 'application/json'}
		body: JSON.stringify(from: 'Website <web@acme.example>', to: 'you@acme.example', reply_to: form.email, subject: 'New enquiry', text: form.message)
	)
	throw new Error("Email failed: {res.status}") unless res.ok

Running it

imba-pages start

compiles the site into .pages/build/ (a self-contained entry.js plus public/) and runs it. To deploy, copy that folder to any host that runs Node (Render, Fly.io, Railway, a VPS) and run node entry.js, with PORT and your keys as environment variables.

Posts also work under dev. A static build still renders the page, but the build prints a note: nothing in dist/ can receive the post.

One page, both ways

mode says how the site is running: 'dev', 'static' or 'server'. A contact page can use Netlify Forms in the static build and its own post elsewhere:

import {mode} from '@milktop/imba-pages'

const netlify = mode == 'static'
export const post = netlify ? undefined : handlePost