# OpenSEO Development Workflow: Local Setup to Production Deployment

> Master the OpenSEO development workflow with our guide. Set up locally and deploy to production quickly using TypeScript, pnpm, Vite, and Drizzle ORM. Get started in minutes.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: how-to-guide
- Published: 2026-07-26

---

**The OpenSEO development workflow uses a TypeScript monorepo with pnpm, Vite, and Drizzle ORM, enabling contributors to install dependencies, configure a local SQLite database, and start a dev server within minutes using commands like `pnpm install`, `pnpm run db:migrate:local`, and `pnpm dev:agents`.**

The `every-app/open-seo` repository follows a modern TypeScript monorepo pattern designed for rapid SEO tool development. Understanding the OpenSEO development workflow allows you to spin up a full local environment, run automated tests against your changes, and deploy to production using either Docker or Cloudflare Workers.

## Prerequisites and Environment Setup

Getting started requires Node.js 20+ and pnpm installed on your machine. The workflow is intentionally lightweight, with environment configuration centralized in specific utility files.

### Installing Dependencies

Run `pnpm install` from the repository root to pull in all packages across the monorepo. This installs Vite, Drizzle ORM, TanStack Server Functions, and development tools defined in the root [`package.json`](https://github.com/every-app/open-seo/blob/main/package.json).

### Database Configuration

OpenSEO uses Drizzle ORM to abstract database operations, supporting both SQLite (default) and PostgreSQL. Initialize your local database by running the migration script once after cloning:

```bash
pnpm run db:migrate:local

```

This command invokes the migration logic defined in [`src/db/runBatch.ts`](https://github.com/every-app/open-seo/blob/main/src/db/runBatch.ts), which applies pending schema changes to your local SQLite instance. For subsequent schema changes, you will generate new migrations using `pnpm run db:generate` before running the migrate command again.

### Environment Variables

Configuration management is handled by [`scripts/cli-utils.ts`](https://github.com/every-app/open-seo/blob/main/scripts/cli-utils.ts), which parses CLI arguments and loads environment files. Copy the example environment file and add your DataForSEO API credentials:

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

```

Edit `.env.local` to include your `DATAFORSEO_API_KEY` as a base64-encoded string (`login:password`). The CLI utilities in [`scripts/cli-utils.ts`](https://github.com/every-app/open-seo/blob/main/scripts/cli-utils.ts) automatically load `.env.local` or `.env` depending on your execution context.

## Development Server Options

OpenSEO provides two ways to run the development server, catering to different testing needs.

### Standard Vite Development

For basic development, run:

```bash
pnpm run dev

```

This starts the Vite development server using the configuration defined in [`web/vite.config.ts`](https://github.com/every-app/open-seo/blob/main/web/vite.config.ts). Standard development uses standard localhost ports.

### Portless Development for Agents

The recommended approach for multi-branch testing or AI agent integration uses the portless configuration:

```bash
mkdir -p .logs && touch .logs/dev-server.log
pnpm dev:agents

```

This launches the server at `http://open-seo.localhost:1355` using Vercel's portless tool, giving each Git worktree a unique subdomain. Logs pipe automatically to `.logs/dev-server.log`, and the setup integrates with the MCP (Model Context Protocol) server exposed in `src/middleware/*`, allowing AI agents like Claude Code to consume SEO data directly.

## Project Architecture and Code Organization

The monorepo separates concerns into distinct directories to maintain clarity as the codebase scales.

### Server Functions Structure

All backend logic lives in `src/serverFunctions/` as **TanStack Server Functions**. These are thin wrappers that call shared services. For example, the Google Search Console integration in [`src/serverFunctions/gsc.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/gsc.ts) wraps the DataForSEO client:

```typescript
// src/serverFunctions/gsc.ts
import { createServerFunction } from "@tanstack/server";
import { fetchGscData } from "../lib/gsc-client";

export const getGscOverview = createServerFunction(
  async (params: { domain: string }) => {
    const data = await fetchGscData(params.domain);
    return data;
  },
);

```

Shared business logic resides in `src/shared/` (e.g., [`src/shared/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/rank-tracking.ts) contains core rank-tracking algorithms used by both frontend and backend).

### Database Layer with Drizzle ORM

The database abstraction supports multiple backends through `src/db/pg/*` for PostgreSQL and default SQLite configurations. Migration generation and execution are handled via CLI commands that invoke [`src/db/runBatch.ts`](https://github.com/every-app/open-seo/blob/main/src/db/runBatch.ts). After modifying any schema files, generate a new migration with:

```bash
pnpm run db:generate

```

Then apply it locally using `pnpm run db:migrate:local`.

### Authentication Modes

OpenSEO supports three distinct authentication modes controlled by the `AUTH_MODE` environment variable: `cloudflare_access`, `local_noauth`, and `hosted`. The middleware layer in `src/middleware/ensure-user/*` enforces the selected mode. For Cloudflare Access deployments, validation logic resides in [`src/middleware/ensure-user/cloudflareAccess.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/cloudflareAccess.ts), while type definitions are exported from [`src/middleware/ensure-user/types.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/types.ts).

## Testing and Quality Assurance

Before submitting changes, run the comprehensive CI check locally:

```bash
pnpm ci:check

```

This executes linting, TypeScript type checking, unit tests via Vitest (configured in [`vitest.config.ts`](https://github.com/every-app/open-seo/blob/main/vitest.config.ts)), and end-to-end tests via Playwright (configured in [`playwright.config.ts`](https://github.com/every-app/open-seo/blob/main/playwright.config.ts)). Test files follow the `src/**/*.test.ts` pattern throughout the repository.

## Contribution and Deployment

The contribution workflow is documented in [`docs/CONTRIBUTING.md`](https://github.com/every-app/open-seo/blob/main/docs/CONTRIBUTING.md). Keep pull requests focused on single features or bug fixes, and ensure `pnpm ci:check` passes locally before pushing.

For production deployment, OpenSEO supports two primary methods:

1. **Docker**: Self-host using the provided `Dockerfile` and documentation in [`docs/SELF_HOSTING_DOCKER.md`](https://github.com/every-app/open-seo/blob/main/docs/SELF_HOSTING_DOCKER.md)
2. **Cloudflare Workers**: Deploy to the edge using configurations in the `cloudflare/` directory

Both methods respect the environment variable patterns established in `.env.example` and the authentication modes defined in the middleware layer.

## Summary

- **Install dependencies** with `pnpm install` (requires Node.js 20+)
- **Configure the database** by running `pnpm run db:migrate:local` after cloning
- **Start development** using `pnpm dev:agents` for portless multi-branch testing or `pnpm run dev` for standard development
- **Organize code** in `src/serverFunctions/` for API endpoints, `src/shared/` for utilities, and `src/middleware/` for authentication
- **Test changes** with `pnpm ci:check` before opening pull requests
- **Deploy** via Docker or Cloudflare Workers using the documented configuration patterns

## Frequently Asked Questions

### What is the difference between `pnpm run dev` and `pnpm dev:agents`?

`pnpm run dev` starts a standard Vite development server on localhost ports, suitable for basic frontend development. `pnpm dev:agents` launches a portless instance at `http://open-seo.localhost:1355` that generates unique subdomains per Git worktree and pipes logs to `.logs/dev-server.log`, specifically designed for testing AI agent integrations with the MCP server.

### How do I add a new database migration in OpenSEO?

After modifying your Drizzle schema files, run `pnpm run db:generate` to create a new migration file. Then apply it to your local database using `pnpm run db:migrate:local`. The migration logic is implemented in [`src/db/runBatch.ts`](https://github.com/every-app/open-seo/blob/main/src/db/runBatch.ts) and handles both SQLite and PostgreSQL backends.

### Which authentication mode should I use for local development?

For local development, set `AUTH_MODE=local_noauth` in your `.env.local` file. This bypasses authentication checks and allows immediate access to all server functions. For production deployments, choose `cloudflare_access` (for Cloudflare Zero Trust) or `hosted` depending on your infrastructure, with validation middleware located in `src/middleware/ensure-user/`.

### Where are the server functions located and how are they structured?

Server functions are grouped in `src/serverFunctions/` and implemented as TanStack Server Functions. Each file exports functions created with `createServerFunction` that wrap shared logic from `src/shared/` or external API clients like the DataForSEO client in [`src/lib/auth-client.ts`](https://github.com/every-app/open-seo/blob/main/src/lib/auth-client.ts). This pattern keeps API endpoints type-safe and co-located with their business logic.