Vercel + Deno

The Deno runtime running on Vercel.

โœจ Features

โ“ Frequently Asked Questions

What's the difference with vercel-community/deno?

This is a full rewrite of the unmaintained vercel-community/deno (see #159).

This implementation has been designed from the ground up to be more aligned with Deno standards. For instance, you can directly reuse code written for deno serve and handlers for Deno.serve, and it also natively supports deno shebangs, which minimizes the friction required to deploy existing Deno code to Vercel.

It also provides better support for advanced use-cases, such as specifying environment variables, pre-caching assets, or selecting the Deno version to use through pragmas, and the permissions management system is fully supported.

Why use Vercel instead of Deno Deploy?

While Deno Deploy is a great platform, Vercel free tier offers more generous limits for serverless applications.

If you are deploying a fully-fledged Deno application (which requires to be constantly running, requires storage or database, writable filesystem, etc.), Deno Deploy is likely a better fit.

If you are deploying a simple Deno application with simple functions and callbacks, then Vercel with this runtime is a great choice. Note that the free tier of Vercel limits the number of serverless function to 12.

๐Ÿฆ• Examples

deno_handler.ts

Serverless function that receive a Request and return a Response, using the same interface as handlers used by Deno.serve.

#!/usr/bin/env -S deno run
const serve: Deno.ServeHandler = (_: Request) => {
  return new Response(`๐Ÿฆ• deno version: ${Deno.version.deno}`)
}
export default serve
 

deno_handler_serve.ts

Serverless function that receive a Request and return a Response, using the same interface as exports used by deno serve.

#!/usr/bin/env -S deno serve
export default {
  fetch() {
    return new Response(`๐Ÿฆ• deno version: ${Deno.version.deno}`)
  },
} satisfies Deno.ServeDefaultExport
 

import_dynamic.ts

Example of a serverless function using dynamic imports.

Since the file system is read-only once a function is deployed, the deno cache must be set to a writable directory in order for dynamic imports to work properly. This can be done by setting the environment variable DENO_DIR to /tmp.

#!/usr/bin/env -S deno serve
//@vercel: --env DENO_DIR=/tmp
export default {
  async fetch() {
    // deno-lint-ignore no-import-prefix
    const { say } = await import("jsr:@libs/testing@5/imports")
    return new Response(say(`๐Ÿ“ฆ dynamic import: ${say()}`))
  },
} satisfies Deno.ServeDefaultExport
 

import_http.ts

Example of a serverless function using HTTP(S) imports.

#!/usr/bin/env -S deno serve
// deno-lint-ignore no-import-prefix
import { say } from "https://deno.land/x/libs@2025.11.20/testing/imports/mod.ts"

export default {
  fetch() {
    return new Response(say(`๐Ÿ“ฆ https import: ${say()}`))
  },
} satisfies Deno.ServeDefaultExport
 

import_jsr.ts

Example of a serverless function using jsr: imports.

โ„น๏ธ Dependencies imported via jsr: are vendored and cached at build time.

#!/usr/bin/env -S deno serve
// deno-lint-ignore no-import-prefix
import { say } from "jsr:@libs/testing@5/imports"

export default {
  fetch() {
    return new Response(say(`๐Ÿ“ฆ jsr import: ${say()}`))
  },
} satisfies Deno.ServeDefaultExport
 

import_npm.ts

Example of a serverless function using npm: imports.

โ„น๏ธ Dependencies imported via npm: are vendored and cached at build time.

#!/usr/bin/env -S deno serve
// deno-lint-ignore no-import-prefix
import { say } from "npm:@lowlighter/testing@5/imports"

export default {
  fetch() {
    return new Response(say(`๐Ÿ“ฆ npm import: ${say()}`))
  },
} satisfies Deno.ServeDefaultExport
 

lambda_chromium.ts

Example of a serverless function using @astral/astral to launch a headless Chromium browser, using the full power of deno permissions system.

โ„น๏ธ This function might take a bit longer on cold starts due to the browser initialization.

#!/usr/bin/env -S deno serve --allow-env --allow-read --allow-write=/tmp --allow-run --allow-net
// deno-lint-ignore-file no-import-prefix
import { launch } from "jsr:@lowlighter/astral@0.5.5"
import { chromium } from "jsr:@libs/toolbox@0.1.4/download-lambda-chromium"

export default {
  async fetch() {
    await using browser = await launch({ path: await chromium({ path: "/tmp/chromium" }), launchPresets: { lambdaInstance: true } })
    await using page = await browser.newPage("https://example.com", { sandbox: { permissions: { net: ["example.com"] } } })
    return new Response(`๐ŸŒ browser capture: ${await page.evaluate(() => document.title)}`)
  },
} satisfies Deno.ServeDefaultExport
 

pragma_env.ts

Example of a serverless function configuring environment variables through the --env pragma.

You can use the dropdown input below to print the value of the selected environment variable.

#!/usr/bin/env -S deno serve --allow-env
//@vercel: --env FOO=bar
export default {
  async fetch(request) {
    const { input } = await request.json()
    if (!allowed.includes(input))
      return new Response(`โŒ env: ${input} is not allowed`, { status: 400, headers: { "Content-Type": "application/json" } })
    return new Response(`๐Ÿฆด env: ${input}=${Deno.env.get(input)}`, { headers: { "Content-Type": "application/json" } })
  },
} satisfies Deno.ServeDefaultExport
 

pragma_include.ts

Example of a serverless function including additional files through the --include pragma.

Vercel requires files to be explicitly included in the deployment bundle so if your entrypoint references any local files that are not statically analyzed, you will need to use this pragma to explicitly include them (glob patterns are supported).

#!/usr/bin/env -S deno serve --allow-read
//@vercel: --include LICENSE
export default {
  async fetch() {
    const content = (await Deno.readTextFile("LICENSE")).split("\n").slice(0, 1)
    return new Response(`๐Ÿ“ƒ file content: ${content}`)
  },
} satisfies Deno.ServeDefaultExport
 

pragma_version.ts

Example of a serverless function using a fixed Deno version through the --version pragma.

By default, the runtime uses the latest stable Deno version that was available at build time.

#!/usr/bin/env -S deno serve
//@vercel: --version 2.5.0
export default {
  fetch() {
    return new Response(`๐Ÿฆ• deno version: ${Deno.version.deno}`)
  },
} satisfies Deno.ServeDefaultExport
 

rock_paper_scissors.ts

Example of an interactive serverless function that reads a Request.body provided by an end-user and and plays a game of Rock Paper Scissors against them.

#!/usr/bin/env -S deno run
const text = { rock: "๐Ÿชจ Rock", paper: "๐Ÿ“œ Paper", scissors: "โœ‚๏ธ Scissors" }

export default {
  async fetch(request) {
    const { input } = await request.json()
    const cpu = ["rock", "paper", "scissors"][Math.floor(Math.random() * 3)]
    switch (true) {
      case (input === "rock") && (cpu === "scissors"):
      case (input === "paper") && (cpu === "rock"):
      case (input === "scissors") && (cpu === "paper"):
        return new Response(`You chose [${text[input]}], I chose [${text[cpu]}]. You win! ๐ŸŽ‰`)
      case (input === "rock") && (cpu === "paper"):
      case (input === "paper") && (cpu === "scissors"):
      case (input === "scissors") && (cpu === "rock"):
        return new Response(`You chose [${text[input]}], I chose [${text[cpu]}]. I win! ๐Ÿค–`)
      default:
        return new Response(`We both chose [${text[input]}]. It's a tie! ๐Ÿค`)
    }
  },
} satisfies Deno.ServeDefaultExport
 

shebang.ts

Example of a serverless function with extra permissions and options specified through a shebang.

#!/usr/bin/env -S deno serve --allow-sys=osRelease
export default {
  fetch() {
    return new Response(`๐Ÿ’ป os release: ${Deno.osRelease()}`)
  },
} satisfies Deno.ServeDefaultExport
 

๐Ÿ““ Usage

Add the following to your vercel.json file:

// vercel.json
{
  "functions": {
    "api/**/*.ts": { "runtime": "@lowlighter/vercel-deno@2.5.6" }
  }
}

A serverless function may be defined using an export default of one of the following types:

See /api for examples.

๐Ÿช› Advanced Usage

Shebangs

Runtime options and permissions may be specified using deno shebangs.

#!/usr/bin/env -S deno run --allow-sys
export default async function serve() {
  return new Response(Deno.osRelease())
}

โš ๏ธ Important
The parser only supports shebangs that use deno run or deno serve.

โ„น๏ธ Note
The --allow-read permission for the function's source file is always implicitly granted as it is required to load and run the handler. Under the hood, the handler is run within a WebWorker with the provided permissions.

Pragma

Specific instructions may be provided to the runtime using the //@vercel: pragma (which must be placed at the top of the file or immediately after the shebang).

OptionAliasDescriptionMultipleDevDefaultExample
--version-vSpecify the Deno version to use.Nยนlatest-v 2.5.6
--env-eSpecify environment variables to set.YY-e FOO=bar
--include-iSpecify additional modules to pre-cache.YNยฒ-i /assets
//@vercel: -e FOO=bar
export default async function serve() {
  return new Response(Deno.env.get("FOO"))
}

Deno in build steps

The Deno runtime is only made available by Vercel at runtime during serverless function execution.

If you wish to use it in build steps, you can use the following commands in your vercel.json:

// vercel.json
{
  "installCommand": "curl -fsSL https://deno.land/install.sh | sh",
  "buildCommand": "([ -d /vercel ] && /vercel/.deno/bin/deno task build) || true"
}