# Local development

> Run vercel dev via package.json script, local .env from .env.example, and exercising upload, /f proxy, and delete against a dev deployment.

- Repository: MaxLeiter/vcup
- GitHub: https://github.com/MaxLeiter/vcup
- Human docs: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb
- Complete Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/llms-full.txt

## Source Files

- `package.json`
- `.env.example`
- `vercel.json`
- `cli.ts`
- `api/upload.ts`
- `api/f.ts`

---

---
title: "Local development"
description: "Run vercel dev via package.json script, local .env from .env.example, and exercising upload, /f proxy, and delete against a dev deployment."
---

The repository’s local server entry point is the `dev` npm script, which runs `vercel dev` and serves `api/upload.ts`, `api/delete.ts`, and the `/f/:slug*` rewrite to `api/f.ts` with environment variables loaded from a gitignored `.env` file patterned on `.env.example`.

## Prerequisites

| Requirement | Role |
| --- | --- |
| [Vercel CLI](https://vercel.com/docs/cli) | Runs `vercel dev` and loads project env for serverless handlers |
| Vercel project link | `vercel dev` expects a linked Vercel project in this directory (creates `.vercel/`, gitignored) |
| Vercel Blob store | `put` / `del` in upload and delete handlers need a real `BLOB_READ_WRITE_TOKEN`; proxy serving needs `BLOB_STORE_URL` |
| Bun (recommended) | `cli.ts` is a Bun script (`#!/usr/bin/env bun`); use `bun ./cli.ts` from the repo or a global install |

<Note>
Local development still talks to a live Blob store. There is no in-memory or filesystem blob backend in this repository—copy production-like credentials into `.env`, or create a dedicated Blob store for dev.
</Note>

## Environment file

Copy the example env file and fill in values from the Vercel dashboard (**Storage** tab after deploy, or the same fields wired by the README deploy button):

```bash
cp .env.example .env
```

| Variable | Required for | Notes |
| --- | --- | --- |
| `BLOB_READ_WRITE_TOKEN` | Upload, delete | Used by `@vercel/blob` `put` / `del` (see `.env.example` comment) |
| `BLOB_STORE_URL` | `/f` proxy, CLI proxy delete | Public store URL, e.g. `https://….public.blob.vercel-storage.com` |
| `VCUP_TOKEN` | Upload, delete API | Shared secret; must match the CLI’s `token` / `VCUP_TOKEN` |
| `VCUP_BASE_URL` | Upload JSON `url` field | Optional; overrides host-derived base URL in upload responses |

`.gitignore` excludes `.env` and `.env*.local`, so secrets stay out of git.

## Start the dev server

From the repository root:

<CodeGroup>
```bash title="bun"
bun run dev
```

```bash title="npm"
npm run dev
```
</CodeGroup>

The `dev` script in `package.json` is exactly `vercel dev`. Vercel loads `.env` for server handlers and applies `vercel.json` rewrites locally:

```json
{ "source": "/f/:slug*", "destination": "/api/f" }
```

Use the origin URL that `vercel dev` prints (commonly `http://localhost:3000`) as the CLI `url` / `VCUP_API_URL`—do not assume a fixed port if your CLI shows another.

## Point the CLI at the dev server

The CLI resolves config from `VCUP_API_URL` + `VCUP_TOKEN` first, then `~/.vcuprc`. For local work, either export env vars in the shell or use a dev-only rc file.

<RequestExample>
```bash
export VCUP_API_URL="http://localhost:3000"
export VCUP_TOKEN="same-value-as-in-env"
export BLOB_STORE_URL="https://your-store.public.blob.vercel-storage.com"
```
</RequestExample>

<ResponseExample>
```json title="~/.vcuprc (alternative)"
{
  "url": "http://localhost:3000",
  "token": "same-value-as-in-env"
}
```
</ResponseExample>

<ParamField body="VCUP_API_URL" type="string" required>
Base origin of the vcup deployment (no trailing path). Uploads hit `{url}/api/upload`; deletes hit `{url}/api/delete`.
</ParamField>

<ParamField body="VCUP_TOKEN" type="string" required>
Must equal server `VCUP_TOKEN` in `.env` for Bearer auth on upload and delete.
</ParamField>

<ParamField body="BLOB_STORE_URL" type="string">
Required on the **client** when deleting by proxy URL (`/f/...`); the CLI maps slug → `{BLOB_STORE_URL}/{slug}` before calling delete.
</ParamField>

Run the CLI from the repo without publishing:

```bash
bun ./cli.ts path/to/file.png
```

## Exercise upload

<Steps>
<Step title="Start vercel dev">
Ensure `.env` has `BLOB_READ_WRITE_TOKEN`, `BLOB_STORE_URL`, and `VCUP_TOKEN`, then run `bun run dev` or `npm run dev`.
</Step>

<Step title="Upload a file">
With dev env vars set, upload a small file:

```bash
bun ./cli.ts ./README.md
```

The CLI POSTs to `/api/upload` with `Authorization: Bearer …` and `X-Filename`, streaming the body with a progress bar on stderr.
</Step>

<Step title="Verify response">
On success, stdout is one line: the proxy URL (`url`) unless `--raw` was passed (then `raw` blob URL). Upload handler shape:

```json
{ "url": "<base>/f/<slug>", "raw": "<blob-store-url>" }
```

`base` is `VCUP_BASE_URL` if set; otherwise `x-forwarded-proto` (default `http`) + `host` from the request—on local dev that is typically your `vercel dev` origin.
</Step>
</Steps>

<Warning>
Upload rejects missing `X-Filename` (400), wrong method (405), or token mismatch (401). The CLI refuses directories client-side before calling the API.
</Warning>

## Exercise the `/f` proxy

Open the printed proxy URL in a browser or fetch it:

```bash
curl -I "http://localhost:3000/f/<slug-from-upload>"
```

Flow:

1. `vercel.json` rewrites `/f/:slug*` → `/api/f`.
2. `api/f.ts` strips the `/f/` prefix, builds `{BLOB_STORE_URL}/{slug}`, fetches upstream, and streams the body with `Content-Disposition: inline` and a MIME type from the slug filename.

If `BLOB_STORE_URL` is unset server-side, the handler returns **500** with `BLOB_STORE_URL not configured`. Upstream failures yield **502**; missing slug **400**.

## Exercise delete

<Tabs>
<Tab title="Proxy URL">
```bash
export BLOB_STORE_URL="https://your-store.public.blob.vercel-storage.com"
bun ./cli.ts rm "http://localhost:3000/f/<slug>"
```

The CLI resolves `/f/` slugs using client `BLOB_STORE_URL`, then `DELETE /api/delete` with `X-Blob-Url` set to the raw blob URL.
</Tab>

<Tab title="Raw blob URL">
```bash
bun ./cli.ts rm "https://….public.blob.vercel-storage.com/<slug>"
```

No client `BLOB_STORE_URL` needed; the URL is sent as-is in `X-Blob-Url`.
</Tab>
</Tabs>

Successful delete prints `Deleted` and the API returns **200**. Auth and header errors mirror upload (401 without valid Bearer, 400 without `X-Blob-Url`).

## Local vs production behavior

```mermaid
sequenceDiagram
  participant CLI as cli.ts
  participant Dev as vercel dev
  participant Upload as api/upload.ts
  participant Blob as Vercel Blob
  participant Proxy as api/f.ts

  CLI->>Dev: POST /api/upload + Bearer + X-Filename
  Dev->>Upload: handler
  Upload->>Blob: put (addRandomSuffix)
  Upload-->>CLI: JSON url + raw
  CLI->>Dev: GET /f/slug (browser or curl)
  Dev->>Proxy: rewrite /f → /api/f
  Proxy->>Blob: fetch BLOB_STORE_URL/slug
  Proxy-->>CLI: streamed inline body
  CLI->>Dev: DELETE /api/delete + X-Blob-Url
  Dev->>Blob: del
```

| Concern | Local (`vercel dev`) | Production (Vercel deployment) |
| --- | --- | --- |
| Server env | `.env` in repo root | Vercel project environment variables |
| Proxy base in upload JSON | Request host or `VCUP_BASE_URL` | Deployment host, custom domain, or `VCUP_BASE_URL` |
| Blob storage | Same remote store unless you use a separate dev store | Project-linked Blob store |
| CLI target | `VCUP_API_URL` / `~/.vcuprc` `url` → dev origin | Production deployment URL |

<Info>
Set `VCUP_BASE_URL` in `.env` when proxy links must always use a specific origin (for example a stable tunnel URL or custom domain) instead of whatever `host` header `vercel dev` sends.
</Info>

## Quick verification checklist

| Step | Command / action | Expected signal |
| --- | --- | --- |
| Server up | `bun run dev` | Vercel dev ready message with local URL |
| Upload | `bun ./cli.ts <file>` | One-line `http://…/f/…` URL on stdout |
| Proxy | Open that URL | File content inline; correct `Content-Type` when extension is known |
| Delete | `bun ./cli.ts rm <url>` | `Deleted`; blob removed (re-fetch proxy → 404) |
| Auth | Mismatch `VCUP_TOKEN` | `Upload failed: 401` or delete 401 |

## Common local issues

| Symptom | Likely cause |
| --- | --- |
| `Missing config` from CLI | No `VCUP_API_URL`/`VCUP_TOKEN` and no `~/.vcuprc` |
| Upload `401 Unauthorized` | CLI token ≠ `.env` `VCUP_TOKEN` |
| Proxy `500 BLOB_STORE_URL not configured` | `.env` missing `BLOB_STORE_URL` on server |
| `Cannot resolve proxy URL without BLOB_STORE_URL` on `vcup rm` | Client env missing `BLOB_STORE_URL` when deleting `/f/` URLs |
| Upload succeeds but `/f` 404 | Slug/store mismatch; confirm `BLOB_STORE_URL` matches the store used by `put` |
| `502 Failed to fetch file` | Wrong store URL or blob not public upstream |

See the troubleshooting page for status-code detail across handlers.

## Related pages

<CardGroup>
<Card title="Deploy on Vercel" href="/deploy-vercel">
Create the Blob store and obtain `BLOB_READ_WRITE_TOKEN` and store URL for `.env`.
</Card>
<Card title="Environment variables" href="/environment-variables">
Server and client variable reference including optional `VCUP_BASE_URL`.
</Card>
<Card title="Configure the CLI" href="/configure-cli">
`~/.vcuprc` schema and env precedence for targeting a dev origin.
</Card>
<Card title="Upload and delete" href="/upload-and-delete">
CLI upload streaming, `--raw`, and delete URL forms in depth.
</Card>
<Card title="API reference" href="/api-reference">
Handler methods, headers, and response codes for `/api/upload`, `/api/delete`, and `/f`.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Source-backed failure modes for auth, proxy, and delete paths.
</Card>
</CardGroup>
