---
name: editframe-dev-server
description: "Local dev server for Editframe compositions. Adds on-demand video transcoding, local asset serving, and URL signing with no cloud dependency, through the Vite plugin, the Next.js plugin, or the framework-agnostic dev-server package."
license: PROPRIETARY
metadata:
  packages:
    - "@editframe/vite-plugin"
    - "@editframe/nextjs-plugin"
    - "@editframe/dev-server"
  version: 0.59.0-beta.25
---


# Dev Server

Editframe compositions reference local media directly, for example `<ef-video src="clip.mp4">`. A browser cannot decode a raw video file or fetch a local caption transcript by itself. The Editframe dev server fills that gap. It adds on-demand video transcoding, local image and caption serving, and URL signing, with no cloud API dependency.

Pick one integration, based on your framework.

- **Vite** (the `html`/`react` templates from `npm create @editframe`): `@editframe/vite-plugin`.
- **Next.js** (the `nextjs` template): `@editframe/nextjs-plugin`.
- **Any other toolchain** (Webpack, Rspack, a custom Node.js server): `@editframe/dev-server` directly.

All three wrap the same implementation. A scaffolded project already includes the right one. See the `editframe-create` skill.

## Vite Setup

```typescript
// vite.config.ts
import { defineConfig } from "vite";
import { vitePluginEditframe } from "@editframe/vite-plugin";

export default defineConfig({
  plugins: [
    vitePluginEditframe({
      root: "./src",       // base directory local `src=` paths resolve against
      cacheRoot: "./cache", // directory for cached transcoded assets
    }),
  ],
});
```

The plugin mounts onto Vite's own dev server. Compositions make same-origin requests. No separate port and no CORS setup are necessary.

## Next.js Setup

```javascript
// next.config.mjs
import { withEditframe } from "@editframe/nextjs-plugin";

export default withEditframe(
  { root: "./src", cacheRoot: "./cache" },
  {
    // your existing Next.js config
  },
);
```

`withEditframe` starts a sidecar HTTP server next to `next dev`, on port 3099 by default. Override the port with a `port` option. It also adds `rewrites()` rules that proxy Editframe's requests to that sidecar, so compositions still make same-origin requests. Run `next dev` as usual. No other setup is necessary.

The sidecar does not start in production. Deploy against real media (cloud renders, uploaded files) instead.

## Framework-Agnostic Setup

For a toolchain with no Vite or Next.js integration, call `@editframe/dev-server` directly. Get six asset-processing functions from `@editframe/assets`. Provide URL-signing handlers with `createProdEfHandlers`.

```typescript
import { createEditframeDevServer, createProdEfHandlers } from "@editframe/dev-server";
import {
  generateTrack,
  generateScrubTrack,
  generateTrackFragmentIndex,
  cacheImage,
  findOrCreateCaptions,
  md5FilePath,
} from "@editframe/assets";
import { Client, createURLToken } from "@editframe/api";

const server = createEditframeDevServer(
  { root: "./src", cacheRoot: "./cache" },
  { generateTrack, generateScrubTrack, generateTrackFragmentIndex, cacheImage, findOrCreateCaptions, md5FilePath },
  createProdEfHandlers({
    createURLToken,
    getClient: () => new Client(process.env.EF_TOKEN),
  }),
);

server.listen(3001, () => console.log("Editframe dev server running on http://localhost:3001"));
```

If your toolchain already exposes a Connect-compatible middleware stack (Express, Connect, or a custom Vite integration), call `createEditframeRouter(...)` with the same three arguments instead. Mount the result with `.use()`. `createEditframeDevServer` wraps that same router in its own standalone `http.Server`, for toolchains with no middleware stack of their own.

## What It Enables

Once one integration runs, these capabilities work with no manual configuration and no direct requests from your own code.

- **JIT video transcoding.** Reference a local video file directly, for example `<ef-video src="clip.mp4">`. The dev server transcodes it into streamable segments on first request, then serves cached segments after that.
- **Local image and caption serving.** `<ef-image>` and caption generation both work against local files with no setup.
- **A local files API** that mirrors the shape of the production files API. Code written against local media keeps working after you switch to uploaded or cloud files.
- **URL signing.** `ef-configuration`'s default `signing-url` forwards to the real Editframe cloud API to mint a playback token. Set the `EF_TOKEN` environment variable before you start your dev server. Set `EF_HOST` to point at a non-default API host.

See the `composition` skill for how `ef-video`, `ef-image`, and `ef-captions` consume local media. See the `editframe-api` skill for `EF_TOKEN` and `EF_HOST`.

## Visual Regression Testing (Vite Only)

This feature requires the Vitest-specific entry point. It adds server-side [odiff-bin](https://github.com/nicolo-ribaudo/odiff-bin) image comparison, since native binaries are not available in the browser.

```typescript
// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig(async () => {
  const { vitePluginEditframe } = await import("@editframe/vite-plugin/src/index.vitest.js");
  return {
    plugins: [vitePluginEditframe({ root: "./test-assets", cacheRoot: "./test-assets" })],
    test: { browser: { enabled: true, provider: "playwright" } },
  };
});
```

Call it from your own test code, with no wrapper library:

```typescript
const response = await fetch("/@ef-compare-snapshot", {
  method: "POST",
  body: JSON.stringify({
    testName: "my-component",
    snapshotName: "default-state",
    dataUrl: canvas.toDataURL("image/png"),
    threshold: 0.1,               // color-distance threshold (odiff), lower = stricter, default 0.1
    antialiasing: true,           // ignore antialiasing-only differences, default true
    acceptableDiffPercentage: 1.0, // max % differing pixels before failure, default 1.0
  }),
});
const result = await response.json(); // { match: boolean, diffCount, diffPercentage, baselineCreated?, error? }
```

When no baseline exists yet, this call saves the actual image as the baseline. It returns `{ match: true, baselineCreated: true }`. Two related endpoints cover the other cases: `/@ef-compare-two-images` compares two arbitrary images with no baseline, and `/@ef-write-snapshot` writes a baseline or actual image directly.

Snapshots live under `{root}/test/__snapshots__/{testName}/{snapshotName}.{baseline,actual,diff}.png`.

## Debug Logging

Set `DEBUG` before you start your dev server, to trace a specific subsystem.

```bash
DEBUG=ef:dev-server:jit npm run dev      # transcode middleware
DEBUG=ef:dev-server:assets npm run dev   # local image/caption serving
DEBUG=ef:dev-server:files npm run dev    # local files API
DEBUG=ef:dev-server:sign-url npm run dev # URL signing
DEBUG=ef:dev-server:snapshot npm run dev # visual regression testing
```
