iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article

Tutorial: Running Remix v2 with Cloudflare Pages and D1

に公開
1

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.

https://developers.cloudflare.com/workers/wrangler/install-and-update/

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.

https://remix.run/docs/en/main/other-api/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.

https://remix.run/docs/en/main/guides/templates

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.

https://developers.cloudflare.com/pages/framework-guides/deploy-a-remix-site/

First, prepare the command.
Add "deploy": "npm run build && wrangler pages deploy ./public" to the scripts in package.json.

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.

https://developers.cloudflare.com/workers/wrangler/configuration/#d1-databases

First, create wrangler.toml.

touch wrangler.toml

Next, enter the configuration for the created D1.

wrangler.toml
+[[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.

https://developers.cloudflare.com/d1/platform/migrations/

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.

migrations/0000_users.sql
 -- 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.

https://developers.cloudflare.com/pages/platform/functions/bindings/#d1-databases

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.

https://developers.cloudflare.com/d1/examples/d1-and-remix/

It seems that the following information has changed starting from Remix v2:

  • LoaderArgsLoaderFunctionArgs
  • context.DBcontext.env.DB
app/routes/_index.tsx
@@ -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.
_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:

https://dev.classmethod.jp/articles/deploy-remix-cloudflare-pages/
https://zenn.dev/creamstew/articles/e341af02cf17e6c2dda5
https://zenn.dev/mizchi/articles/d1-drizzle-orm
https://zenn.dev/chimame/scraps/563e81420738c2

Discussion