Understanding how to make Cloudflare Pages compatible with Vite, which became stable with Remix 2.7
Tags: react, remix, cloudflare, vite
Published: 2024-03-01 02:33:35

Remix 2.7 has been released. From 2.7 onwards, Vite compatibility, which has been unstable until now, has been adopted as the official version.
Prior to 2.7, the Node.js runtime provided something that worked, but not something that worked with Cloudflare Pages. However, at the same time as the release of 2.7, something that works with Cloudfalre Pages was released, so I will summarize the results of my research on what has changed and how it is being handled.

https://github.com/remix-run/remix/blob/main/CHANGELOG.md#v270

Several bugs were fixed after the release of 2.7.0, so we recommend using the bug-fixed version of the migration instead of 2.7.0.
Also, there is an official document for migrating to the Vite version, so I think reading it will help you understand better.

https://remix.run/docs/en/main/future/vite#migrating

## Differences in initial `package.json`

First, let's compare the differences in the initial packages between the conventional version and the Vite version.

### Differences in installation commands

Please note that the templates are separate, so the traditional version and the Vite version are different.

```bash
npx create-remix@latest --template remix-run/remix/templates/cloudflare-pages
```

```bash
npx create-remix@latest --template remix-run/remix/templates/vite-cloudflare
```

### Difference between npm script and npm packages

The `package.json` actually generated from the above command differs as follows.

![Difference between npm script and npm packages](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/54hqltbdmrhoqsyurw6z.png)

Of course, Vite etc. are added to the installed packages. You can see that the packages are different, but you can also see that the npm scripts are also different.

- Added npm scripts for `deploy` `postinstall` and `typegen`
- Add `dev` and change `start`

There have been many changes, and I will explain each of the changes.


## What are `typegen` and `postinstall`?

First, I will explain the additional npm scripts `typegen` and `postinstall`.
`postinstall` allows you to define commands that are automatically started after executing the npm install command. Therefore, in the Vite version, the script defined in `postinstall` will be executed by executing the command equivalent to npm install. The important content of `postinstall` is simply reading the `typegen` npm script. So the main body is a `typegen` npm script.

### The true identity of `typegen`

I'm hitting a command called `wrangler types` which is what `typegen` is doing. You can understand what this is doing by reading the Wrangler documentation.

https://developers.cloudflare.com/workers/wrangler/commands/#types

In short, it generates `worker-configuration.d.ts`, the type file required for the application, from the `wrangler.toml` file used in Cloudflare's development environment and deployment.

Specifically, if you have a `wrangler.toml` file like the following

```toml:wrangler.toml
vars = { API_HOST = "example.com" }

kv_namespaces = [
  { id = "MY_KV", binding="MY_KV" }
]

d1_databases = [
  { binding = "DB", database_name = "TestDB", database_id = "test-db-id" }
]
```

```typescript
interface Env {
	MY_KV: KVNamespace;
	API_HOST: "example.com";
	DB: D1Database;
}
```

This `worker-configuration.d.ts` type file is very important as it will be used later in Remix. This is because this file corresponds to the previous version, `remix.env.d.ts`. The Vite version does not use `remix.env.d.ts`, so be sure to generate it as written in the migrate documentation.

### Notes on type generation

It's convenient because it automatically generates a type file, but there are some things to keep in mind.
What it is is the way `wrangler.toml` is written. In the previous example, there are two ways to write `vars`, and I wrote one of them, but the other is written as follows.

```toml
# Changed vars = { API_HOST = "example.com" }
[vars]
API_HOST = "example.com"

kv_namespaces = [
  { id = "MY_KV", binding="MY_KV" }
]

d1_databases = [
  { binding = "DB", database_name = "TestDB", database_id = "test-db-id" }
]
```

This is also a correct description, but if you run `typegen`'s npm script in this state, `worker-configuration.d.ts` like the following will be created.

```typescript
interface Env {
	API_HOST: "example.com";
	kv_namespaces: [{"id":"MY_KV","binding":"MY_KV"}];
	d1_databases: [{"binding":"DB","database_name":"TestDB","database_id":"test-db-id"}];
}
```

This is not the type file format that Remix is aiming for, so be careful when writing it.

### `wrangler.toml` trap in Cloudflare Pages

A very big trap lurks. The README of Cloudflare Pages, which is compatible with the Vite version of Remix, has the following description.

```markdown
> [!WARNING]  
> Cloudflare does _not_ use `wrangler.toml` to configure deployment bindings.
> You **MUST** [configure deployment bindings manually in the Cloudflare dashboard][bindings].
```

Since it generates the form, wouldn't it also be reflected during deployment? For some reason, `wrangler pages deploy` does not reflect the contents of `wrangler.toml`. So, I'm telling you to first configure it on the Cloudflare console.

## Two ways to launch a development environment

In the Vite version of 2.7, there are two methods. That's the modified `dev` and `start` npm script. The previous version of dev's npm script is written as follows.

```json
  "scripts": {
    ...
    "dev": "remix dev --manual -c \"npm run start\"",
    ...
    "start": "wrangler pages dev --compatibility-date=2023-06-21 ./public",
    ...
  },
```

So, if you start `dev`, it will start up to `start`. (The conventional version is also built using `remix dev`.) So, what's going on with the Vite version?

```json
  "scripts": {
    ...
    "dev": "remix vite:dev",
    ...
    "start": "wrangler pages dev ./build/client",
    ...
  },
```

`dev` and `start` are not linked. Works as separate commands. Regarding `dev`, some commands that work with Vite are defined. As for `start`, the command to start with `wrangler` is defined in the built file as before.
So what has changed here is that `start` starts the development environment using `wrangler`, which is almost the same as the previous version, and `dev` is the command to start the development environment with the newly added Vite.

### The reality of `remix vite:dev`

Let's find out what the newly added `remix vite:dev` is. The explanation will be long if you follow from the cli command, so I will only write the main points.
Since it is the Vite version, when you look at the Vite configuration `vite.config.ts`, you will see the following description for Cloudflare called `cloudflareDevProxyVitePlugin`.

```typescript
import {
  vitePlugin as remix,
  cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
  plugins: [
    remixCloudflareDevProxy(),
    remix(),
    tsconfigPaths()
  ],
});
```

In this guy's code, we get to the reality of Vite Server, a so-called development environment.

https://github.com/remix-run/remix/blob/df0a668d416014f19313419dc7701ddcbe4ee312/packages/remix-dev/vite/cloudflare-proxy-plugin.ts#L59-L85

In this `configureServer`, we call the `wranlger` API called `getPlatformProxy`. It is no exaggeration to say that this is the reality of `remix vite:dev`.

https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy

Simply put, `wrangler` acts as a proxy, and Vite Server actually runs. So, why does `wrangler` need to act as a proxy? What is returned from this `getPlatformProxy` is that running Cloudflare's own services (such as D1, R2, KV, etc.) on the Vite Server side returns the API. That is a major feature. I wrote a program to access D1 in the Remix code in the development environment, and it works on Vite Server because Remix on Vite Server runs the Cloudflare environment API that was proxied with `getPlatformProxy`.

### Notes on `remix vite:dev`

If you have read this far and have the following questions, you are in good hands.

**I want to check the operation in a workerd environment.**

that's right. It's still the same as `wrangler` providing Cloudflare's API and running on Vite Server, that is, Node server. Therefore, since it is not a `wranglerd` environment, code that depends on Node's API will run without doing anything.
I think that's one of the reasons why the `start` npm script remains in order to finally check the operation. On the `start` side, we will use `wrangler` to raise the development environment, so we will check the operation in the workerd environment.

“Then why don’t we just say `start`?”

I certainly think so. However, there are other problems with `start`, and you need to be careful if you use it regularly.

https://github.com/remix-run/remix/issues/7466

Since the Vite version has just been released, there are some issues, but efforts to improve this have already begun, so I think it would be best to wait patiently, or since it is OSS, it would be better to cooperate.

## Build process up to deployment in Vite version

The method of starting the development environment has certainly changed with Vite, but the main focus is building. Since it is a Vite version, of course the build will be done with Vite. By the way, the conventional version is built using esbuild.

### The reality of `remix vite:build`

Previously, Remix generated various options for building with esbuild and built it, but I will write about how it is done in the Vite version. This can also be seen by reading from `vite.config.ts` in the same way as `remix vite:dev`.

```typescript
import {
  vitePlugin as remix,
  cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
  plugins: [
    remixCloudflareDevProxy(),
    remix(),
    tsconfigPaths()
  ],
});
```

Here we set something called `vitePlugin` as a Vite plugin. This is the main body that contains the settings for building.

https://github.com/remix-run/remix/blob/df0a668d416014f19313419dc7701ddcbe4ee312/packages/remix-dev/vite/plugin.ts#L530

This will return multiple Vite plugin configurations for building Remixes. Among them, the following four are the ones you should read if you want to know about build-related processes.

1. `name: "remix"`: which returns the Vite build config settings
2. `name: "remix-virtual-modules"`: Regenerates server-side code to build only Remix server-side code.
3. `name: "remix-dot-server"`: Validates Remix's `.server` file from being mixed into client code.
4. `name: "remix-dot-client"`: Validates Remix's `.client` file in server code.

So, if you want to customize the options for building with Vite, you can also change the return value of `name: "remix"` that is returned at the beginning of the array.
If you have been confused about not being able to change the build settings of esbuild until now, please try changing to the Vite version, as you can change the build as you like.

This will build it with Vite and generate client code and server code in `build/client/` and `build/server/`.

By the way, in order to build for the server, I am doing something quite clumsy to build the files in `app/routes` as one file.

https://github.com/remix-run/remix/blob/df0a668d416014f19313419dc7701ddcbe4ee312/packages/remix-dev/vite/plugin.ts#L704-L750

As you can see, the build method has changed in the Vite version, not only for Cloudflare.

### Build for Cloudflare Pages

Just having the results built with Vite will not work with Cloudflare Pages. If you want to perform server-side processing, you will need a function called Cloudflare Pages Functions that links Cloudflare Pages and Cloudflare Workers.

In the previous version, functions/[[path]].js, which is Cloudflare Pages Functions, was generated by building with Remix. The Vite version hasn't gotten this far yet.

However, the Vite version provides a file called `functions/[[path]].ts` from the beginning.

```typescript
import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - the server build file is generated by `remix vite:build`
// eslint-disable-next-line import/no-unresolved
import * as build from "../build/server";

export const onRequest = createPagesFunctionHandler({ build });
```

In the previous version, it was used as a place to place the JavaScript result of the build, but in the Vite version, TypeScript has been provided from the beginning, and `build/server/index.js` is loaded in it. `wrangler (pages)` recognizes a directory called `functions/` as Cloudflare Pages Functions, but it supports not only JavaScript but also TypeScript.

https://developers.cloudflare.com/pages/functions/typescript/

So, by doing this, we changed the method to generate Cloudflare Pages Functions that include the built JavaScript of Remix's server-side processing when running `wrangler pages dev` and `wrangler pages deploy`.

Note that after all, you need to `remix vite:build` before `start(wranlger pages dev)` will work.


As you can see, Cloudflare Pages compatible with the Vite version of Remix was made possible not only by Remix, but also by Wrangler and Vite.
Although there are some issues, the development experience with Remix has been further improved by moving to Vite, so please give it a try.