π Exploring Faster and Free Hosting
I wanted a faster and free hosting solution for my website so I started exploring my options. After evaluating several platforms I decided to use Cloudflare Pages.
The best part is I connected my GitHub repository to Cloudflare Pages. Now every time I make a change in the repository the website refreshes automatically. This makes updates seamless and keeps deployment friction-free.
ποΈ Backend Setup
Originally my website was built on Umbraco using a clean template. To make it compatible with static hosting I used a custom website exporter to export the entire site into static HTML.
Then I pushed the exported website to my GitHub repository. Thanks to the GitHub integration with Cloudflare Pages the static site automatically updates every time I push changes.
π¬ Adding a Dynamic Contact Form
Even though the site is static I wanted a dynamic contact form to collect messages. For this I used Cloudflare Pages Functions together with D1 database (Cloudflareβs serverless SQLite).
Here is the workflow I implemented:
Contact Form Frontend
Built a simple HTML form with
name
,email
andmessage
fieldsUsed JavaScript
fetch()
to POST the data to a serverless function
Serverless Function (
functions/api/submit.js
)Receives POST requests from the form
Inserts the data into a D1 database table called
contacts
Returns a success message to the user
Database Setup
Created the
contacts
table using a migration SQL file
CREATE TABLE IF NOT EXISTS contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL,
message TEXT NOT NULL,
created_at TEXT NOT NULL
);
Applied the migration using
wrangler d1 migrations apply mydb
Viewing Submissions
Built a simple dashboard page (
submissions.html
) to fetch and display submissions from D1Added a delete button for each entry to manage messages directly from the dashboard
Security
Optional: secure the dashboard with a secret token or Cloudflare Access
1. Contact Form (Frontend)
index.html
<form id="contact-form">
<input type="text" name="name" required placeholder="Your Name">
<input type="email" name="email" required placeholder="Your Email">
<textarea name="message" required placeholder="Your Message"></textarea>
<button type="submit">Send</button>
</form>
<script>
document.getElementById("contact-form").addEventListener("submit", async (e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.target).entries());
const res = await fetch("/api/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
alert(res.ok ? "Message sent!" : "Error sending message");
if (res.ok) e.target.reset();
});
</script>
2. Function to Save Submissions
functions/api/submit.js
export async function onRequestPost({ request, env }) {
const data = await request.json();
const { name, email, message } = data;
if (!name || !email || !message) {
return new Response("Missing fields", { status: 400 });
}
await env.DB.prepare(
"INSERT INTO contacts (name, email, message, created_at) VALUES (?, ?, ?, datetime('now'))"
).bind(name, email, message).run();
return new Response("Saved", { status: 200 });
}
3. Function to List Submissions
functions/api/list.js
export async function onRequestGet({ env }) {
try {
const { results } = await env.DB.prepare(
"SELECT id, name, email, message, created_at FROM contacts ORDER BY created_at DESC"
).all();
return new Response(JSON.stringify(results), {
headers: { "Content-Type": "application/json" }
});
} catch (err) {
return new Response("Error: " + err.message, { status: 500 });
}
}
4. Dashboard Page
submissions.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Contact Submissions</title>
<style>
body { font-family: sans-serif; margin: 20px; }
table { border-collapse: collapse; width: 100%; margin-top: 20px; }
th, td { border: 1px solid #ccc; padding: 8px; }
th { background: #f2f2f2; }
</style>
</head>
<body>
<h1>📬 Contact Submissions</h1>
<table id="submissions-table">
<thead>
<tr><th>ID</th><th>Name</th><th>Email</th><th>Message</th><th>Date</th></tr>
</thead>
<tbody></tbody>
</table>
<script>
async function loadSubmissions() {
const res = await fetch("/api/list");
const data = await res.json();
const tbody = document.querySelector("#submissions-table tbody");
tbody.innerHTML = "";
data.forEach(row => {
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${row.id}</td>
<td>${row.name}</td>
<td>${row.email}</td>
<td>${row.message}</td>
<td>${row.created_at}</td>
`;
tbody.appendChild(tr);
});
}
loadSubmissions();
</script>
</body>
</html>
5. Database Setup
Migration: migrations/0001_init.sql
CREATE TABLE IF NOT EXISTS contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL,
message TEXT NOT NULL,
created_at TEXT NOT NULL
);
Apply it:
npx wrangler d1 migrations apply mydb
6. Wrangler Config
wrangler.toml
[[d1_databases]]
binding = "DB" # must match env.DB
database_name = "mydb"
database_id = "<your-database-id>"
Also in Cloudflare Dashboard β Pages β Settings β Functions β D1 Databases β Add Binding (DB
β mydb
).
7. Deploy
npx wrangler pages deploy ./dist
β‘ Result
The website is now fully static, hosted on Cloudflare Pages for speed and free hosting
The contact form is dynamic, storing data in a Cloudflare D1 database
Submissions can be viewed and deleted via a dashboard
Every change in GitHub automatically redeploys the site
π Summary
By combining static hosting, serverless functions and D1 database I was able to make a fast, free and fully functional website with dynamic capabilities.
This approach is perfect for anyone who wants the performance of a static website but still needs backend functionality like forms and data storage