Skip to content

feat: add Vercel deployment adapter#193

Open
rturnq wants to merge 9 commits into
mainfrom
claude/vercel-adapter
Open

feat: add Vercel deployment adapter#193
rturnq wants to merge 9 commits into
mainfrom
claude/vercel-adapter

Conversation

@rturnq

@rturnq rturnq commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a new adapter package, @marko/run-adapter-vercel, for previewing and deploying Marko Run apps to Vercel via the Build Output API (v3). Running marko-run build emits a .vercel/output directory, which Vercel uses directly — no output directory configuration required.

  • Targets Vercel's Node.js runtime: SSR target node with noExternal: true; the function handler wraps the app's fetch handler with createMiddleware (the same helper the Node adapter uses) so it runs as a standard (req, res) listener. Static assets are served by Vercel's filesystem layer. (The Edge runtime is intentionally not supported — Vercel has deprecated Edge Functions in favor of the Node.js runtime on Fluid Compute.)
  • buildEnd assembles the Build Output API tree: functions/index.func/ with a nodejs20.x .vc-config.json (Nodejs launcher), a package.json (type: module), and the bundled entry; static/ containing the client assets; and a routing config.json that serves the filesystem first and falls back to the function.
  • marko-run preview serves the built .vercel/output locally with no Vercel account, project link, or CLI required. It delegates to the base adapter's preview with a small entry that serves static/ via serve-static + compression (immutable caching for /assets/*) and falls back to the built function — mirroring the static-first routing in the generated config.json. This also means --env files and node args (e.g. --inspect) work like the other adapters.
  • Route handlers receive the underlying Node request/response via the exported VercelNodePlatformInfo type.

Also adds a test fixture to the @marko/run package (vercel-adapter-node) covering both dev and preview — because preview runs on plain Node, CI exercises the full build → serve → hydrate flow with Playwright.

Motivation and Context

Vercel is one of the most widely used deployment platforms, and Marko Run shipped no adapter for it. Targeting the Build Output API gives a zero-config deploy path alongside the existing Node, static, and Netlify adapters.

An earlier revision shelled out to vercel dev for preview, but vercel dev cannot serve Build Output API directories at all — it requires linking a real Vercel project, deletes .vercel/output, and rebuilds from source. Preview is now a small local server over the built output instead. An edge option was also dropped since Vercel deprecated the Edge runtime.

Notes for reviewers

The preview entry passes a next callback to the function's middleware so unmatched routes get a 404 and errors a 500 instead of hanging the request.

Screenshots (if appropriate):

Checklist:

  • I have updated/added documentation affected by my changes.
  • I have added tests to cover my changes.

Add @marko/run-adapter-vercel for deploying Marko Run apps to Vercel via
the Build Output API (v3), emitting a `.vercel/output` directory.

- Node.js Serverless Functions by default; `{ edge: true }` targets the
  Edge runtime (webworker SSR target with the edge-light export condition)
- Node mode wraps the fetch handler with createMiddleware as the function
  handler; static assets are served by Vercel's filesystem layer
- Generates functions/index.func with the appropriate .vc-config.json,
  copies static assets to output/static, and writes a routing config.json
- Exposes platform info per runtime via VercelEdgePlatformInfo /
  VercelNodePlatformInfo
- Adds run-package test fixtures for both modes (preview skipped; it needs
  the Vercel CLI)
@changeset-bot

changeset-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8406d06

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@marko/run-adapter-vercel Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR introduces a new @marko/run-adapter-vercel package that enables deploying Marko Run applications to Vercel Node.js Serverless Functions. It includes the adapter's core implementation (src/index.ts), platform type definitions, default Node entrypoint, preview server (preview-entry.mjs), an esbuild-based build script, package metadata/configuration files, README documentation, a changeset, and a new test fixture (vercel-adapter-node) with routes, a counter component, and test steps.

Changes

Cohort / File(s) Summary
Types & Entry: src/types.ts, src/default-node-entry.ts Adds VercelNodePlatformInfo interface extending NodePlatformInfo; adds default Node request handler composing createMiddleware(fetch).
Adapter Core: src/index.ts, src/preview-entry.mjs Implements vercelAdapter() with Node-only Vite SSR config, startDev/startPreview entry switching, buildEnd generating Vercel Build Output API v3 layout, typeInfo, and copyDir helper; adds preview HTTP server serving static assets and delegating to built middleware.
Build Tooling & Metadata: scripts/build.ts, scripts/importMetaURL.js, package.json, package.toggle.json, tsconfig.json, README.md, .changeset/vercel-adapter.md Adds esbuild-based multi-format build script, import.meta.url shim, package manifest with compression/serve-static deps and @marko/run peer dependency, exports/types toggle config, TS config, documentation, and changeset.
Test Fixture: packages/run/src/__tests__/fixtures/vercel-adapter-node/* Adds fixture project with .gitignore, package.json, generated route types, a counter.marko component, +page.marko, test.config.ts steps, tsconfig.json, and vite.config.ts wiring the adapter.

Sequence Diagram(s)

sequenceDiagram
  participant ViteBuild
  participant vercelAdapter
  participant FileSystem
  participant PreviewEntry
  participant MiddlewareEntry

  ViteBuild->>vercelAdapter: buildEnd()
  vercelAdapter->>FileSystem: copy static assets, write function files, .vc-config.json, config.json
  PreviewEntry->>FileSystem: verify output/functions/index.func/index.js exists
  PreviewEntry->>MiddlewareEntry: dynamic import(index.js)
  MiddlewareEntry-->>PreviewEntry: handle request or decline
  PreviewEntry-->>PreviewEntry: respond 404/500 on fallback
Loading

Related issues: None specified.

Related PRs: None specified.

Suggested labels: enhancement, package: adapter-vercel, documentation

Suggested reviewers: DylanPiercey

🥕

A hop, a build, a Vercel deploy,
Static assets tucked away, employed.
Node functions bundled, ready to run,
Counter clicks beneath the sun.
New rabbit adapter joins the crew — 🐇 hooray, a fixture too!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a Vercel deployment adapter.
Description check ✅ Passed The description is directly related to the changeset and accurately describes the new Vercel adapter and preview flow.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/vercel-adapter

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/adapters/vercel/package.json`:
- Around line 16-22: The package entrypoints in the vercel adapter are pointing
at src/index.ts, but only dist is published. Update the package.json
exports["."] and types fields to reference the built output in dist instead of
source so consumers can resolve the package entry and generated CJS/ESM
artifacts through the published files.

In `@packages/adapters/vercel/README.md`:
- Around line 68-72: The example GET edge handler uses the wrong second-argument
shape, so destructuring { platform } there will be undefined. Update the sample
so GET reads platform from the handler context argument instead of the next
argument, and keep the waitUntil(logRequest()) call using the platform value
obtained from that context.

In `@packages/adapters/vercel/scripts/build.ts`:
- Around line 11-12: The Vercel adapter is declaring a Node 14 target while
`packages/adapters/vercel/src/index.ts` relies on `fs.cp()`, which is not
available there. Update the adapter build/runtime contract by either raising the
supported Node version in `build.ts` along with the package `engines`/docs, or
replace the `fs.cp()` usage in the Vercel adapter with a Node 14-compatible copy
implementation so the declared target and runtime code match.

In `@packages/adapters/vercel/scripts/importMetaURL.js`:
- Around line 1-2: The injected CJS helper in importMetaURL.js is exporting a
URL object instead of a string, which breaks consumers like
path.dirname(import.meta.url) during initialization. Update the __importMetaURL
export in this helper to return a string value rather than a URL object, keeping
the change localized to the __importMetaURL symbol so downstream imports receive
the expected format.

In `@packages/adapters/vercel/src/index.ts`:
- Around line 185-193: The assertVercelCLI function currently calls
process.exit(1) when execSync("vercel --version") fails, which makes this
library-level failure uncatchable; update the catch block to throw an error
instead of exiting so callers can handle it through the normal error path. Keep
the existing warning context in assertVercelCLI, but replace the parent-process
termination with a thrown exception that includes the missing-CLI message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 59c366dc-359e-4a9a-8ba0-b79e8b301b0f

📥 Commits

Reviewing files that changed from the base of the PR and between 779776f and 70be870.

⛔ Files ignored due to path filters (3)
  • package-lock.json is excluded by !**/package-lock.json and included by **
  • packages/run/src/__tests__/fixtures/vercel-adapter-edge/__snapshots__/dev.expected.md is excluded by !**/__snapshots__/** and included by **
  • packages/run/src/__tests__/fixtures/vercel-adapter-node/__snapshots__/dev.expected.md is excluded by !**/__snapshots__/** and included by **
📒 Files selected for processing (26)
  • .changeset/vercel-adapter.md
  • packages/adapters/vercel/README.md
  • packages/adapters/vercel/package.json
  • packages/adapters/vercel/scripts/build.ts
  • packages/adapters/vercel/scripts/importMetaURL.js
  • packages/adapters/vercel/src/default-edge-entry.ts
  • packages/adapters/vercel/src/default-node-entry.ts
  • packages/adapters/vercel/src/index.ts
  • packages/adapters/vercel/src/types.ts
  • packages/adapters/vercel/tsconfig.json
  • packages/run/src/__tests__/fixtures/vercel-adapter-edge/.gitignore
  • packages/run/src/__tests__/fixtures/vercel-adapter-edge/.marko-run/routes.d.ts
  • packages/run/src/__tests__/fixtures/vercel-adapter-edge/package.json
  • packages/run/src/__tests__/fixtures/vercel-adapter-edge/src/components/counter.marko
  • packages/run/src/__tests__/fixtures/vercel-adapter-edge/src/routes/+page.marko
  • packages/run/src/__tests__/fixtures/vercel-adapter-edge/test.config.ts
  • packages/run/src/__tests__/fixtures/vercel-adapter-edge/tsconfig.json
  • packages/run/src/__tests__/fixtures/vercel-adapter-edge/vite.config.ts
  • packages/run/src/__tests__/fixtures/vercel-adapter-node/.gitignore
  • packages/run/src/__tests__/fixtures/vercel-adapter-node/.marko-run/routes.d.ts
  • packages/run/src/__tests__/fixtures/vercel-adapter-node/package.json
  • packages/run/src/__tests__/fixtures/vercel-adapter-node/src/components/counter.marko
  • packages/run/src/__tests__/fixtures/vercel-adapter-node/src/routes/+page.marko
  • packages/run/src/__tests__/fixtures/vercel-adapter-node/test.config.ts
  • packages/run/src/__tests__/fixtures/vercel-adapter-node/tsconfig.json
  • packages/run/src/__tests__/fixtures/vercel-adapter-node/vite.config.ts

Comment thread packages/adapters/vercel/package.json
Comment thread packages/adapters/vercel/README.md Outdated
Comment thread packages/adapters/vercel/scripts/build.ts
Comment thread packages/adapters/vercel/scripts/importMetaURL.js Outdated
Comment thread packages/adapters/vercel/src/index.ts Outdated
rturnq added 6 commits July 1, 2026 04:58
- Add package.toggle.json so publish points exports/types at dist/
- Make the injected import.meta.url shim a string (.href) for the CJS build
- Fix the README platform example to read from context.platform
- Replace fs.cp() with a recursive copy helper so the adapter works on
  older Node versions (fs.cp is unavailable before Node 16.7)
- Throw instead of exiting the process when the Vercel CLI is missing
Use `shell: true` only on Windows (where `vercel` is a `.cmd` shim that
requires it). On POSIX the CLI is spawned directly, so `close()` kills the
CLI itself instead of an orphaned shell wrapper and argument values are no
longer re-parsed by the shell. Also handle the child's error event.
Clarify that Marko Run automatically uses an installed adapter with no
Vite config, and add a Deploying section right after installation covering
the Git integration and `npm run build` + `npx vercel deploy --prebuilt`.
`marko-run preview` previously shelled out to `vercel dev`, which requires
linking to a real Vercel project (hence the `--yes` prompt) and, worse,
cannot serve a Build Output API v3 directory at all — it removes
`.vercel/output` and rebuilds from source, failing when there is no build
script it can run.

Replace it with a small in-process server that serves the generated
`.vercel/output` directly: static assets from `static/` (the `filesystem`
handler) and everything else through the built Node/Edge function, mirroring
the routing the adapter's `config.json` describes. Preview now runs entirely
locally with no Vercel account, project link, or CLI required.

Open sockets are tracked and destroyed on `close()` so keep-alive
connections don't block shutdown. The node/edge preview fixtures no longer
need to be skipped, so they now exercise this path in CI.
Vercel deprecated Edge Functions in 2025 in favor of the Node.js runtime
on Fluid Compute, so the adapter's `edge` option targeted a runtime Vercel
is winding down. Remove it and everything downstream: the edge entry, the
webworker SSR build config, the edge `.vc-config.json` variant, the
`VercelEdgePlatformInfo` type, and the edge preview path. The adapter now
takes no options and always builds for the Node.js runtime.

The preview server loses its runtime branch and always invokes the built
function as a `(req, res)` listener, still serving static assets from the
build output first.
Replace the hand-rolled in-process preview server with the same pattern the
base adapter uses: `startPreview` delegates to the base adapter with a small
`preview-entry.mjs`, which it spawns as a child process (gaining `--env`
file loading and node args like `--inspect` for free). The entry mirrors
the base `default-entry.mjs` — `serve-static` + `compression` over
`.vercel/output/static` with immutable asset caching — and falls back to
the built function imported from `.vercel/output/functions`, preserving
Vercel's static-first routing.

This deletes the custom HTTP server, static file resolver, and MIME map in
favor of the off-the-shelf libraries already used elsewhere in the repo.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/adapters/vercel/src/index.ts (1)

49-71: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Copy all SSR chunks into the Vercel function output. builtEntries[0] only moves the SSR entry chunk; dynamic import() calls can still emit sibling chunks, and those files are not copied into .vercel/output/functions/index.func/, so the deployed function can fail at runtime. Copy the full SSR output or force a single-file SSR bundle with inlineDynamicImports.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapters/vercel/src/index.ts` around lines 49 - 71, The Vercel
adapter’s buildEnd currently only moves builtEntries[0] into the function
output, so any SSR sibling chunks emitted by dynamic imports are left behind.
Update the buildEnd flow in the Vercel adapter to copy the full SSR build output
into .vercel/output/functions/index.func/ or change the SSR bundling strategy to
produce a single file with inlineDynamicImports, making sure the server entry
and all dependent chunks are included.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/adapters/vercel/src/preview-entry.mjs`:
- Around line 47-77: The dynamic import in preview-entry.mjs is not handling
rejection, so a module init failure from import(pathToFileURL(funcEntry).href)
can become an unhandled promise rejection and prevent the server from starting.
Add a rejection path around the existing top-level import(...).then(...) flow in
preview-entry.mjs that logs the import error clearly and exits or returns a
500-style failure instead of silently failing; keep the handling near the
import(pathToFileURL(funcEntry).href) and createServer bootstrap so the startup
error is surfaced to the user.

---

Outside diff comments:
In `@packages/adapters/vercel/src/index.ts`:
- Around line 49-71: The Vercel adapter’s buildEnd currently only moves
builtEntries[0] into the function output, so any SSR sibling chunks emitted by
dynamic imports are left behind. Update the buildEnd flow in the Vercel adapter
to copy the full SSR build output into .vercel/output/functions/index.func/ or
change the SSR bundling strategy to produce a single file with
inlineDynamicImports, making sure the server entry and all dependent chunks are
included.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 72032eb4-b87d-4ed4-9ed9-d1d894bbe0f7

📥 Commits

Reviewing files that changed from the base of the PR and between 134494f and a5b52eb.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json and included by **
  • packages/run/src/__tests__/fixtures/vercel-adapter-node/__snapshots__/preview.expected.md is excluded by !**/__snapshots__/** and included by **
📒 Files selected for processing (7)
  • packages/adapters/vercel/README.md
  • packages/adapters/vercel/package.json
  • packages/adapters/vercel/scripts/build.ts
  • packages/adapters/vercel/src/index.ts
  • packages/adapters/vercel/src/preview-entry.mjs
  • packages/adapters/vercel/src/types.ts
  • packages/run/src/__tests__/fixtures/vercel-adapter-node/test.config.ts
💤 Files with no reviewable changes (2)
  • packages/adapters/vercel/src/types.ts
  • packages/run/src/tests/fixtures/vercel-adapter-node/test.config.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/adapters/vercel/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/adapters/vercel/package.json

Comment thread packages/adapters/vercel/src/preview-entry.mjs Outdated
@rturnq
rturnq force-pushed the claude/vercel-adapter branch from cc7e560 to 6141d34 Compare July 1, 2026 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant