iTranslated by AI
Tutorial: Running Remix v2 with Cloudflare Pages and D1
Remix v2 has been released, and since some types and interfaces have changed, I tried the Remix + Cloudflare Pages + D1 configuration with a minimal setup to verify it works.
Prerequisites
Library versions.
remix v2.0.1
wrangler v3.10.1
Please ensure that wrangler is installed and that you have executed wrangler login.
npx wrangler whoami
⛅️ wrangler 3.10.1
-------------------
Getting User settings...
👋 You are logged in with an OAuth Token, associated with the email xxx@example.com!
┌──────────────────────────────┬──────────────────────────────────┐
│ Account Name │ Account ID │
├──────────────────────────────┼──────────────────────────────────┤
│ xxx@example.com's Account │ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx │
└──────────────────────────────┴──────────────────────────────────┘
🔓 Token Permissions: If scopes are missing, you may need to logout and re-login.
Scope (Access)
- account (read)
- user (read)
- workers (write)
- workers_kv (write)
- workers_routes (write)
- workers_scripts (write)
- workers_tail (read)
- d1 (write)
- pages (write)
- zone (read)
- ssl_certs (write)
- constellation (write)
- offline_access
Setting up the Remix + Cloudflare Pages configuration
Create a project with create-remix
We will set it up using create-remix.
Starting from Remix v2, you specify the template via an argument at execution time.
In this case, we will use the official Cloudflare Pages template provided.
Run the following.
npx create-remix@latest --template remix-run/remix/templates/cloudflare-pages
Configure various settings interactively. In this example, the directory name is set to remix.
$ npx create-remix@latest --template remix-run/remix/templates/cloudflare-pages
remix v2.0.1 💿 Let's build a better website...
dir Where should we create your new project?
remix
◼ Template: Using remix-run/remix/templates/cloudflare-pages...
✔ Template copied
git Initialize a new git repository?
Yes
deps Install dependencies with npm?
Yes
✔ Dependencies installed
✔ Git initialized
done That's it!
Enter your project directory using cd ./remix
Check out README.md for development and deploy instructions.
Join the community at https://rmx.as/discord
Move to the created directory.
cd ./remix
The .node-version file created at this point specifies version 18.0.0.
$ cat .node-version
18.0.0
Change this to your desired version. In this case, we'll change it to 18.18.0.
$ node -v
v18.18.0
echo "18.18.0" > .node-version
That's it for the minimal setup. Let's start it up locally.
$ npm run dev
> dev
> remix dev --manual -c "npm run start"
💿 remix dev
info building...
info built (266ms)
> start
> wrangler pages dev --compatibility-date=2023-06-21 ./public
Compiling worker to "/var/folders/hl/frth8xln7nzb8v671ldj5c_80000gn/T/functionsWorker-0.5842657134185703.mjs"...
✨ Compiled Worker successfully
⛅️ wrangler 3.10.1
-------------------
wrangler dev now uses local mode by default, powered by 🔥 Miniflare and 👷 workerd.
To run an edge preview session for your Worker, use wrangler dev --remote
⎔ Starting local server...
▲ [WARNING] Parsed 2 valid header rules.
[mf:inf] Ready on http://0.0.0.0:8788
[mf:inf] - http://127.0.0.1:8788
[REMIX DEV] c781a4f8 ready
Open http://0.0.0.0:8788. If it displays as shown below, you are good to go.

The file structure looks like this.
$ ls -T
./
├── app/
│ ├── entry.client.tsx
│ ├── entry.server.tsx
│ ├── root.tsx
│ └── routes/
│ └── _index.tsx
├── node_modules/
├── package-lock.json
├── package.json
├── public/
│ ├── _headers
│ ├── _routes.json
│ └── favicon.ico
├── README.md
├── remix.config.js
├── remix.env.d.ts
├── server.ts
└── tsconfig.json
Deployment
In this tutorial, we will deploy via the CLI using wrangler. Alternatively, options such as automatic deployment linked with Git are also available.
First, prepare the command.
Add "deploy": "npm run build && wrangler pages deploy ./public" to the scripts in package.json.
"scripts": {
"build": "remix build",
"dev": "remix dev --manual -c \"npm run start\"",
"start": "wrangler pages dev --compatibility-date=2023-06-21 ./public",
- "typecheck": "tsc"
+ "typecheck": "tsc",
+ "deploy": "npm run build && wrangler pages deploy ./public"
},
Try deploying with the command you prepared.
npm run deploy
Since this is the first deployment, you will be asked for a project name; create one with any name you like.
$ npm run deploy
> deploy
> npm run build && wrangler pages deploy ./public
> build
> remix build
info building... (NODE_ENV=production)
info built (147ms)
No project selected. Would you like to create one or use an existing project?
❯ Create a new project
Use an existing project
✔ Enter the name of your new project: … remix
✔ Enter the production branch name: … master
✨ Successfully created the 'remix' project.
✨ Compiled Worker successfully
🌍 Uploading... (7/7)
✨ Success! Uploaded 7 files (1.78 sec)
✨ Uploading _headers
✨ Uploading Functions bundle
✨ Uploading _routes.json
✨ Deployment complete! Take a peek over at https://229ecb4f.remix-4hc.pages.dev
Open the URL on the last line. If it is displayed, the process is complete. You can also check it from the Cloudflare Dashboard.

Setting up D1
Create the D1 Database
Next, we will create the D1 database. We will also name this "remix".
npx wrangler d1 create remix
$ npx wrangler d1 create remix
✅ Successfully created DB 'remix' in region APAC
Created your database using D1's new storage backend. The new storage backend is not yet recommended for production workloads, but backs up your data via point-in-time
restore.
[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "remix"
database_id = "647377a9-f57f-4e70-9c04-3e659bfcadb2"
The information under [[d1_databases]] is the configuration to connect to D1.
We will add this configuration to wrangler.toml.
First, create wrangler.toml.
touch wrangler.toml
Next, enter the configuration for the created D1.
+[[d1_databases]]
+binding = "DB" # i.e. available in your Worker on env.DB
+database_name = "remix"
+database_id = "647377a9-f57f-4e70-9c04-3e659bfcadb2"
Also, D1-related files will be created under .wrangler, but since these are for local use, we add them to .gitignore.
echo "/.wrangler" >> .gitignore
Migrations
We will create a table using the migration method provided by D1.
You can create a migration file with any name. Here, we'll create a users table, so we named it users.
npx wrangler d1 migrations create remix users
The first time, you will be asked for a directory, so create it with the default.
$ npx wrangler d1 migrations create remix users
✔ No migrations folder found. Set `migrations_dir` in wrangler.toml to choose a different path.
Ok to create ~/migrations? … yes
✅ Successfully created Migration '0000_users.sql'!
The migration is available for editing here
~/migrations/0000_users.sql
This time, we will create a simple table called users.
-- Migration number: 0000 2023-10-02T05:08:21.139Z
+CREATE TABLE `users` (
+ `id` INTEGER PRIMARY KEY AUTOINCREMENT,
+ `username` TEXT NOT NULL
+);
Apply it locally.
npx wrangler d1 migrations apply remix --local
$ npx wrangler d1 migrations apply remix --local
Migrations to be applied:
┌────────────────┐
│ name │
├────────────────┤
│ 0000_users.sql │
└────────────────┘
✔ About to apply 1 migration(s)
Your database may not be available to serve requests during the migration, continue? … yes
🌀 Mapping SQL input into an array of statements
🌀 Loading 647377a9-f57f-4e70-9c04-3e659bfcadb2 from .wrangler/state/v3/d1
┌────────────────┬────────┐
│ name │ status │
├────────────────┼────────┤
│ 0000_users.sql │ ✅ │
└────────────────┴────────┘
Create data in the users table.
$ npx wrangler d1 execute remix --local --command="INSERT INTO users (username) VALUES ('Taro'), ('Hanako')"
🌀 Mapping SQL input into an array of statements
🌀 Loading 647377a9-f57f-4e70-9c04-3e659bfcadb2 from .wrangler/state/v3/d1
Check the records.
$ npx wrangler d1 execute remix --local --command="SELECT * FROM users"
🌀 Mapping SQL input into an array of statements
🌀 Loading 647377a9-f57f-4e70-9c04-3e659bfcadb2 from .wrangler/state/v3/d1
┌────┬──────────┐
│ id │ username │
├────┼──────────┤
│ 1 │ Taro │
├────┼──────────┤
│ 2 │ Hanako │
└────┴──────────┘
We have now confirmed that the local D1 has been created.
Now, reflect this in production as well.
npx wrangler d1 migrations apply remix
npx wrangler d1 execute remix --command="INSERT INTO users (username) VALUES ('Taro'), ('Hanako')"
npx wrangler d1 execute remix --command="SELECT * FROM users"
Execution results.
$ npx wrangler d1 migrations apply remix
Migrations to be applied:
┌────────────────┐
│ name │
├────────────────┤
│ 0000_users.sql │
└────────────────┘
✔ About to apply 1 migration(s)
Your database may not be available to serve requests during the migration, continue? … yes
🌀 Mapping SQL input into an array of statements
🌀 Parsing 2 statements
🌀 Executing on remix (647377a9-f57f-4e70-9c04-3e659bfcadb2):
🚣 Executed 2 commands in 0.6229469776153564ms
┌────────────────┬────────┐
│ name │ status │
├────────────────┼────────┤
│ 0000_users.sql │ ✅ │
└────────────────┴────────┘
$ npx wrangler d1 execute remix --command="INSERT INTO users (username) VALUES ('Taro'), ('Hanako')"
🌀 Mapping SQL input into an array of statements
🌀 Parsing 1 statements
🌀 Executing on remix (647377a9-f57f-4e70-9c04-3e659bfcadb2):
🚣 Executed 1 commands in 0.1962520033121109ms
$ npx wrangler d1 execute remix --command="SELECT * FROM users"
🌀 Mapping SQL input into an array of statements
🌀 Parsing 1 statements
🌀 Executing on remix (647377a9-f57f-4e70-9c04-3e659bfcadb2):
🚣 Executed 1 commands in 0.30927400290966034ms
┌────┬──────────┐
│ id │ username │
├────┼──────────┤
│ 1 │ Taro │
├────┼──────────┤
│ 2 │ Hanako │
└────┴──────────┘
Binding D1
Next, we will bind Pages Functions with D1.
From the Cloudflare Dashboard, open the corresponding Pages > Settings > Functions.

Press "Add bindings" under "D1 database bindings" located around the middle.

Set the following values and press Save.
Variable name: DB
D1 database: remix
Now you can connect to D1 from your production Pages Functions (Workers).
D1 setup is complete.
Calling D1 from Remix
Let's add the logic to call D1 in _index.tsx.
It seems that the following information has changed starting from Remix v2:
-
LoaderArgs→LoaderFunctionArgs -
context.DB→context.env.DB
@@ -1,4 +1,25 @@
-import type { MetaFunction } from "@remix-run/cloudflare";
+import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/cloudflare";
+import { json } from "@remix-run/cloudflare";
+import { useLoaderData } from "@remix-run/react";
+
+interface Env {
+ DB: D1Database;
+}
+
+type User = {
+ id: number;
+ username: string;
+};
+
+export async function loader({ context }: LoaderFunctionArgs) {
+ const env = context.env as Env;
+
+ const { results } = await env.DB.prepare("SELECT * FROM users").all<User>();
+
+ return json({
+ users: results ?? [],
+ });
+}
export const meta: MetaFunction = () => {
return [
@@ -8,6 +29,8 @@ export const meta: MetaFunction = () => {
};
export default function Index() {
+ const { users } = useLoaderData<typeof loader>();
+
return (
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
<h1>Welcome to Remix</h1>
@@ -35,6 +58,9 @@ export default function Index() {
Remix Docs
</a>
</li>
+ {users.map((user) => (
+ <li key={user.id}>{user.username}</li>
+ ))}
</ul>
</div>
);
Here is the full code for index.tsx.
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/cloudflare";
import { json } from "@remix-run/cloudflare";
import { useLoaderData } from "@remix-run/react";
interface Env {
DB: D1Database;
}
type User = {
id: number;
username: string;
};
export async function loader({ context }: LoaderFunctionArgs) {
const env = context.env as Env;
const { results } = await env.DB.prepare("SELECT * FROM users").all<User>();
return json({
users: results ?? [],
});
}
export const meta: MetaFunction = () => {
return [
{ title: "New Remix App" },
{ name: "description", content: "Welcome to Remix!" },
];
};
export default function Index() {
const { users } = useLoaderData<typeof loader>();
return (
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
<h1>Welcome to Remix</h1>
<ul>
<li>
<a
target="_blank"
href="https://remix.run/tutorials/blog"
rel="noreferrer"
>
15m Quickstart Blog Tutorial
</a>
</li>
<li>
<a
target="_blank"
href="https://remix.run/tutorials/jokes"
rel="noreferrer"
>
Deep Dive Jokes App Tutorial
</a>
</li>
<li>
<a target="_blank" href="https://remix.run/docs" rel="noreferrer">
Remix Docs
</a>
</li>
{users.map((user) => (
<li key={user.id}>{user.username}</li>
))}
</ul>
</div>
);
}
Commit to the base branch and deploy.
npm run deploy
Let's open the production Pages site.
If the information from the users table is displayed, you're all set.

Summary
Now that D1 has moved into Beta, I gave it a try and noticed that the implementation had changed slightly with Remix v2, so I wrote this article as a quick memo. I hope this helps someone out.
References
Here are the articles I referred to:
Discussion
Awesome!
D1 の設定を参考にさせていただきました。