iTranslated by AI
Hosting a Site with Cloudflare Pages (Functions and KV): A Practical Guide
Introduction
This article will create a project that leverages Cloudflare Pages as a starting point to explore various aspects of the Cloudflare ecosystem. Specifically, it will cover the following:
- How to use a custom domain with
Cloudflare Pages - How to try out
Cloudflare Functions (beta) - How to use
Cloudflare Worker KV - How to apply IP filter settings to a site hosted on
Cloudflare Pages
Architecture
As shown in the figure below, the architecture involves reading and writing to Cloudflare KV via Cloudflare Functions from a site hosted on Cloudflare with a custom domain. (Hereafter, Cloudflare Functions may be abbreviated as Functions, and Cloudflare KV as KV.)

Environment Information
- Domain acquired with Google Domains (you can use any registrar by replacing it as appropriate)
- Zone Apex domain set for Cloudflare Pages
| Tool | Version |
|---|---|
| node | v16.10.0 |
Cloudflare WebSites Setup
Set up Cloudflare WebSites. This setting is a prerequisite for applying a custom domain.

Enter the domain name you have acquired.

Select the free plan.


Cloudflare will check the domain and display additional setup instructions. In this case, it explains how to change the Name Server on Google Domains. Make a note of the Name Servers.

Go to Google Domains settings. Set the Name Servers you copied from the Cloudflare console earlier.

Return to the Cloudflare console and open WebSites to confirm that the status is Active.

Creating a Cloudflare Pages Project
Project Template Initialization
Initialize the project using vite.
$ npm init vite@latest
✔ Project name: … cf-sample-site
✔ Select a framework: › react
✔ Select a variant: › react-ts
Scaffolding project in /Users/hoge/repos/github.com/shuntaka9576/cf-sample-site...
Done. Now run:
cd cf-sample-site
npm install
npm run dev
$ cd cf-sample-site
$ yarn install
$ yarn dev
Installing Necessary Libraries
Install cloudflare/wrangler2, the official Cloudflare CLI. Although not used in this article, it's good practice to install the type definition files along with it. (It can also be installed with wrangler init.)
$ yarn add -O wrangler@beta
$ yarn add -D @cloudflare/workers-types
$ yarn wrangler --version
(truncated)
0.0.16
Creating and Configuring Cloudflare KV
Initialize the wrangler configuration file.
$ yarn wrangler init
(truncated)
Would you like to install wrangler into your package.json? (y/n)
# => Enter n because it's already installed
Would you like to create a Worker at src/index.ts? (y/n)
# => Enter n because it won't be used
The generated configuration file looks like this:
compatibility_date = "2022-02-07"
Create Cloudflare KV via CLI.
$ yarn wrangler kv:namespace create "MY_KV_DEV"
Executing this command will launch a browser, and after logging in, you will be redirected to an authorization screen like the one below.

After setup is complete, a screen like this will be displayed.

Return to the console and confirm that the KV creation process is complete.
$ yarn wrangler kv:namespace create "MY_KV_DEV"
yarn run v1.22.17
warning package.json: No license field
(truncated)
Successfully configured. You can find your configuration file at: /Users/hoge/.wrangler/config/default.toml
🌀 Creating namespace with title "worker-MY_KV_DEV"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "MY_KV_DEV", id = "xxxxx" }
✨ Done in 12.52s.
After creation, confirm that the created KV exists on the console.

You can also check it from the CLI.
$ yarn wrangler kv:namespace list
[
{
"id": "xxxxx",
"title": "worker-MY_KV_DEV",
"supports_url_encoding": true
}
]
As per the CLI output, add the Cloudflare KV configuration to wrangler.toml.
[ts gutter="false" title="wrangler.toml"]
compatibility_date = "2022-02-07"
kv_namespaces = [
{ binding = "MY_KV_DEV", id = "xxxxx" }
]
Implementing Cloudflare Functions and a UI for verification
We will implement the verification UI and Cloudflare Functions.
Implement the verification UI. (Apologies, I'm not strong in frontend development, please submit an issue if there are any code deficiencies.)
import { useEffect, useState } from "react";
type WriteItem = {
key: string;
value: string;
};
const App = () => {
const [schedulePostData, setSchedulePostData] = useState<WriteItem>();
const [data, setGetData] = useState('');
useEffect(() => {
const timestamp = Date.now();
const key = `key:${timestamp}`;
const value = `value:${timestamp}`;
setSchedulePostData({ key: key, value: value });
}, [])
const updateTime = () => {
const timestamp = Date.now();
const key = `key:${timestamp}`;
const value = `value:${timestamp}`;
setSchedulePostData({ key: key, value: value });
}
const post = async () => {
await fetch("/api/data", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ key: schedulePostData?.key, value: schedulePostData?.value })
});
};
const get = async () => {
const data = await fetch(`/api/data/${schedulePostData?.key}`, {
method: "GET",
headers: {
"Content-Type": "application/json"
},
});
const parsedJson = await data.json();
setGetData(parsedJson.value)
};
return (
<div className="App">
<p>Data to write<button onClick={() => updateTime()}>Update</button></p>
<ul>
<li>Key: {schedulePostData?.key}</li>
<li>Value: {schedulePostData?.value}</li>
</ul>
<button onClick={() => post()}>Write to Cloudflare KV</button>
<hr/>
<p>Get value for key {schedulePostData?.key}</p>
<button onClick={() => get()}>Get</button>
<p>Result: {data}</p>
</div>
);
};
export default App;
The Functions implementation refers to the official documentation.
Implement Functions for POST requests. The following shows a schematic diagram of its behavior.

export const onRequestPost = async ({ request, env }) => {
const body = await request.clone().text();
const parsedBody = JSON.parse(body);
const { key, value } = parsedBody;
await env.MY_KV_DEV.put(key, value); // Write to Cloudflare KV
return new Response();
};
Implement GET Functions to retrieve the data written by the aforementioned Functions. The id will contain the key entered during the POST request. The following shows a schematic diagram of its behavior.

export const onRequestGet = async ({ params, env }) => {
const res = await env.MY_KV_DEV.get(params.id); // Read from Cloudflare KV
return new Response(JSON.stringify({ value: res }));
};
Local Preview
For previewing, we will use wrangler instead of vite. This is because wrangler allows running Cloudflare KV and Cloudflare Functions locally for testing. Building is necessary, so use the following command to trigger a build when changes are detected.
$ yarn build --watch
Run wrangler. A tab will automatically open at http://localhost:8788/ by default.
| Option | Description |
|---|---|
| pages dev | Cloudflare Pages preview |
| -k | Specify Cloudflare KV (a simulated local KV will be run instead of the one created previously) |
| --live-reload | Reload if changes in build assets are detected |
| ./dist | Specify build assets |
$ yarn wrangler pages dev -k MY_KV_DEV --live-reload ./dist
# To set environment variables
$ yarn wrangler pages dev -k MY_KV_DEV --binding ENV_VALUE="3" --live-reload ./dist
When executed, the screen will look like this. It writes a key,value pair to Cloudflare KV and retrieves the written value.

Deploying to Cloudflare Pages
When creating a project for the first time, GitHub Apps authorization is required.

Select the repository to host.

This time, we will simply specify the build command and the asset directory.

The site deployment is complete (it won't work yet).

Set up the integration between Functions and KV.

After performing the above, please re-deploy from the Cloudflare Pages screen: View Build -> Manage deployment -> Retry development (※ This might not be necessary).

This setting will read/write to the actual Cloudflare KV created in the previous section. The written data can also be checked on the console.

Applying a Custom Domain
This time, we will assign an Apex domain like xxx.dev.
Open the Pages settings screen.




After a while, it will become Active.

The custom domain has been successfully applied.

Applying Firewall Rules
Since there might be a need for IP filtering in development environments, we will set it up.


The image below shows settings to block all addresses except the specified IPv4 and IPv6 (range) addresses. (The Rule Name is
ipv4 but it's a typo). Conditions like blocking all but multiple IPs can be achieved by using the is not in clause and entering multiple IPs into a single input box.

If blocked, it will appear as shown below.

Conclusion
In this article, we experimented with KV and Functions, starting with Cloudflare Pages. As CDN Edge technology evolves year by year, I believe there will be an increasing number of cases where it is more efficient to do what was previously done on the server-side on the CDN Edge. I hope this serves as a reference for future technology choices.
Discussion