Deno Deploy
Deploy a Remix 3 app to Deno Deploy, the new platform at console.deno.com, which runs full Deno 2 apps with CDN caching and built-in logs, traces, and metrics. Nothing compiles the app ahead of time. Deploy installs the dependencies of the source you upload, and Deno runs the TypeScript directly. remix/assets compiles each browser module the first time a browser asks for it, the same way it does in development.
Configuration
Deno reads the JSX settings from the starter's tsconfig.json and installs from its package.json, so the app needs no deno.json. It does need an entrypoint. Deploy's Dynamic Entrypoint is executed the way deno run executes a file, so it must start a server. Hand the router to Deno.serve:
import { router } from "./app/router.ts";
Deno.serve({ port: Number(Deno.env.get("PORT") ?? 8000) }, request => router.fetch(request));The starter's app/assets.ts reads NODE_ENV: outside development it stops watching files and minifies what it serves. Add fingerprinting, which is what Remix recommends for a deployment:
export const assets = createAssetServer({
// ...
minify: !isDevelopment,
watch: isDevelopment,
fingerprint: !isDevelopment,
});Every asset URL then carries a content hash, /assets/app/actions/public/entry.@xnRkP3.ts, and is served with Cache-Control: public, max-age=31536000, immutable. Fingerprinting and file watching cannot be combined, so both follow the same flag.
An unset NODE_ENV counts as development, and Deploy does not set it. Add NODE_ENV with the value production to both the Production and Development contexts, so preview URLs run the same way production does. From the CLI, deno deploy env add NODE_ENV production --app my-remix-app adds it to every context at once.
The app's build configuration (dashboard Edit build config, or CLI flags):
| Setting | Value |
|---|---|
| Framework preset | No Preset |
| Install command | deno install |
| Build command | (empty: there is nothing to build) |
| Runtime configuration | Dynamic |
| Dynamic Entrypoint | main.ts |
The upload respects .gitignore, so node_modules stays behind and Deploy's install command restores it on Deploy's own Linux builders. That matters: the asset server compiles with native addons, and installing on Deploy fetches the ones built for Deploy's Linux rather than for your machine.
Local development
npm run dev # the starter's dev server, under NodeTo exercise the deployed entrypoint under Deno, in production mode, before deploying:
deno install
NODE_ENV=production deno run -A main.tsDeploy with the CLI
The deno deploy command comes with the Deno CLI. There is nothing to build first. The CLI tarballs the directory (respecting .gitignore) and uploads it:
deno deploy --app my-remix-app --prod(The first bare deno deploy run authenticates and prompts for an app name.)
Create the app non-interactively (flags switch the wizard off):
deno deploy create \
--org my-org \
--app my-remix-app \
--source local \
--do-not-use-detected-build-config \
--install-command "deno install" \
--runtime-mode dynamic \
--entrypoint main.ts \
--region globalWithout --do-not-use-detected-build-config, a build configuration Deploy detects in the directory takes precedence over the flags. --region also takes us or eu.
create publishes a first revision straight away, before the app has any environment variables, so that revision runs in development mode. Add NODE_ENV and deploy again:
deno deploy env add NODE_ENV production --app my-remix-app
deno deploy --app my-remix-app --prodUseful companions:
deno deploy logs # stream runtime logs
deno deploy env load .env # push env vars; secret-looking keys are auto-marked secretDeploy with GitHub Actions
Create an access token at console.deno.com/account/access-tokens and store it as the DENO_DEPLOY_TOKEN repository secret. The workflow uploads the checkout as it is. It installs nothing, because Deploy does:
name: Deploy
on:
push:
branches: [main]
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- run: deno deploy --app my-remix-app --prod
env:
DENO_DEPLOY_TOKEN: ${{ secrets.DENO_DEPLOY_TOKEN }}Environment variables
Set them per context (Production for production domains, Development for preview/branch URLs) in the dashboard's environment-variables drawer, or push a local file with deno deploy env load .env. They reach the app through Deno.env.get() (and process.env under Node compatibility).