# Open-SEO Best Practices: A Complete Guide to Type-Safe SEO Infrastructure

> Master Open-SEO best practices with our guide. Follow six setup steps for type-safe SEO infrastructure using typed server functions. Deploy Open-SEO effortlessly.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: best-practices
- Published: 2026-08-13

---

**Deploy Open-SEO successfully by following six setup steps—install skills, configure environment, run pre-flight checks, choose Docker or Cloudflare Workers, migrate your database, and leverage typed server functions for end-to-end type safety.**

Open-SEO is a full-stack, TypeScript-first SEO platform built on modern primitives: TanStack Server Functions, Drizzle ORM, Zod validation, and Better-Auth. Understanding open-seo best practices ensures you deploy securely, extend confidently, and avoid common configuration pitfalls. This guide distills the repository's architecture into actionable patterns drawn directly from the every-app/open-seo source code.

---

## Setting Up Open-SEO for Production

### Install the Skill Set First

Before touching code, install the CLI-driven Open-SEO commands that handle project creation, rank tracking, and more:

```bash

# Install the skill set

npx skills add every-app/open-seo

# Or install all available skills

npx skills add every-app/open-seo --skill '*'

```

This exposes commands like `npx open-seo project create` that call typed server functions internally. Reference: [`web/content/docs/skills/setup.md`](https://github.com/every-app/open-seo/blob/main/web/content/docs/skills/setup.md).

### Configure Environment Secrets

Copy the template and fill required values:

```bash
cp .env.example .env

```

Critical secrets include:
- **DataForSEO API key** – for rank tracking APIs
- **Google OAuth client** – for authentication flows

The `.env.example` file lists all required keys without exposing actual values, following twelve-factor app principles.

### Run Pre-Flight Validation

Execute the self-host preflight script before starting services:

```bash
pnpm exec tsx scripts/selfhost-preflight.ts

```

Or rely on [`docker-entrypoint.sh`](https://github.com/every-app/open-seo/blob/main/docker-entrypoint.sh) to run it automatically. The `runSelfhostPreflight()` function in [`src/lib/selfhost-preflight.ts`](https://github.com/every-app/open-seo/blob/main/src/lib/selfhost-preflight.ts) verifies:

- Database connectivity and migration status
- Presence of required environment variables
- Reachability of external APIs (DataForSEO, Google OAuth)

This prevents containers from starting in broken states—a critical open-seo best practice for production deployments.

---

## Deployment Targets: Docker vs. Cloudflare Workers

### Docker Self-Hosting

```bash

# Pull and run the official image

docker compose up -d

```

The `Dockerfile.selfhost` includes automatic preflight checks in its entrypoint. Ideal for local development, VMs, orprivate infrastructure.

### Cloudflare Workers Edge Deployment

```bash

# After configuring wrangler.toml

wrangler publish

```

Reference configuration lives in `badseo/wrangler.jsonc`. Workers deployment provides edge-scale with zero-ops infrastructure—no container management required.

Both paths are officially maintained. Choose Docker for simplicity, Workers for automatic global distribution.

---

## Database Layer with Drizzle ORM

Open-SEO uses Drizzle for compile-time-checked SQL across SQLite, D1, and Postgres. Schema definitions in [`src/db/schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/schema.ts) guarantee column-level safety:

```ts
// src/db/schema.ts
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";

export const projects = sqliteTable("project", {
  id: text("id").primaryKey(),
  name: text("name").notNull(),
  createdAt: integer("created_at", { mode: "timestamp" }).defaultNow(),
});

```

Run migrations before starting the app:

```bash
pnpm drizzle-kit generate
pnpm drizzle-kit migrate

```

The same schema compiles to Postgres via [`drizzle.config.ts`](https://github.com/every-app/open-seo/blob/main/drizzle.config.ts), enabling database portability without code changes.

---

## Typed Server Functions Architecture

Every public endpoint lives under `src/serverFunctions/*` and follows a consistent pattern. The `getProject` function in [`src/serverFunctions/projects.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/projects.ts) demonstrates:

```ts
// src/serverFunctions/projects.ts
import { z } from "zod";
import { eq } from "drizzle-orm";
import { db } from "@/db/client";
import { projects } from "@/db/schema";

export const getProject = async (input: { id: string }) => {
  const schema = z.object({ id: z.string().uuid() });
  const { id } = schema.parse(input);
  return db.select().from(projects).where(eq(projects.id, id));
};

```

Key characteristics:
- **Zod validation** runs before any database call
- **TanStack Server Functions** expose the endpoint with perfect client-side type inference
- **Drizzle ORM** provides type-safe query building

Call from the front-end with full IntelliSense:

```tsx
import { createProject } from "@/serverFunctions/projects";

async function handleCreate() {
  const result = await createProject({ name: "My Blog" });
  // result is fully typed based on the Zod schema
  console.log("Created project:", result);
}

```

---

## Schema-First Validation Patterns

Centralize all contracts in `src/types/schemas/*.ts`. The rank-tracking schema in [`src/types/schemas/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/rank-tracking.ts) shows the pattern:

```ts
// src/types/schemas/rank-tracking.ts
import { z } from "zod";

export const RankTrackingSchema = z.object({
  projectId: z.string().uuid(),
  keyword: z.string().min(1),
  targetUrl: z.string().url(),
  intervalDays: z.number().int().min(1).max(30),
});

```

Benefits:
- API evolution without breaking callers
- Round-trip tests in [`src/types/schemas/rank-tracking.test.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/rank-tracking.test.ts)
- Single source of truth for validation logic

---

## Adding Custom Endpoints

Extend Open-SEO by following the established three-step pattern:

**Step 1: Define the schema**

```ts
// src/types/schemas/custom.ts
import { z } from "zod";

export const CustomInput = z.object({
  foo: z.string(),
  bar: z.number().int(),
});

```

**Step 2: Create the server function**

```ts
// src/serverFunctions/custom.ts
import { CustomInput } from "@/types/schemas/custom";

export const doSomething = async (input: unknown) => {
  const { foo, bar } = CustomInput.parse(input);
  // business logic
  return { success: true, echo: `${foo}-${bar}` };
};

```

**Step 3: Consume from client**

Client code calls `doSomething(...)` with compile-time type safety and runtime validation guaranteed.

---

## Authentication and Security

Open-SEO uses **better-auth** with a Drizzle adapter, configured in [`src/lib/auth.ts`](https://github.com/every-app/open-seo/blob/main/src/lib/auth.ts):

```ts
// src/lib/auth.ts
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { d1Db } from "./db/client";

export const auth = createAuth({
  adapter: drizzleAdapter(d1Db, { /* schema mapping */ }),
  // additional options
});

```

The `src/middleware/ensureUser/*` middleware enforces user presence on protected routes, returning 401 for unauthenticated requests. Reuse this pattern for any custom protected endpoints.

---

## Privacy and Telemetry

Disable telemetry for privacy-first deployments:

```bash
OPENSEO_TELEMETRY_DISABLED=1

# or

DO_NOT_TRACK=1

```

Documented in [`web/content/docs/self-hosting/docker.md`](https://github.com/every-app/open-seo/blob/main/web/content/docs/self-hosting/docker.md), this aligns with modern privacy regulations and user expectations.

---

## Key Files Reference

Bookmark these paths for daily development:

- **Server functions** – [`src/serverFunctions/projects.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/projects.ts) – Project CRUD API
- **Database schema** – [`src/db/schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/schema.ts) – Central type definitions
- **Zod contracts** – [`src/types/schemas/projects.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/projects.ts) – Payload validation
- **Auth layer** – [`src/lib/auth.ts`](https://github.com/every-app/open-seo/blob/main/src/lib/auth.ts) – Better-Auth integration
- **Self-host checks** – [`src/lib/selfhost-preflight.ts`](https://github.com/every-app/open-seo/blob/main/src/lib/selfhost-preflight.ts) – Startup validation
- **Docker config** – `Dockerfile.selfhost` – Production container
- **Workers config** – `badseo/wrangler.jsonc` – Edge deployment

---

## Summary

- **Install skills first** with `npx skills add every-app/open-seo` to enable CLI workflows
- **Validate before starting** using [`scripts/selfhost-preflight.ts`](https://github.com/every-app/open-seo/blob/main/scripts/selfhost-preflight.ts) to catch configuration errors early
- **Choose Docker or Workers** based on your infrastructure needs—both are first-class
- **Run Drizzle migrations** to sync schema across SQLite, D1, or Postgres targets
- **Follow the three-layer pattern** – Zod schema, server function, client call – for type-safe extensions
- **Disable telemetry** with environment variables when privacy compliance requires it

---

## Frequently Asked Questions

### What database does Open-SEO support?

Open-SEO supports **SQLite**, **Cloudflare D1**, and **Postgres** through Drizzle ORM. The same schema definitions in [`src/db/schema.ts`](https://github.com/every-app/open-seo/blob/main/src/db/schema.ts) compile to all three targets via [`drizzle.config.ts`](https://github.com/every-app/open-seo/blob/main/drizzle.config.ts), enabling seamless database portability without code changes.

### How does Open-SEO validate API inputs?

All inputs pass through **Zod schemas** defined in `src/types/schemas/*.ts`. Server functions parse and validate payloads before database access, providing both runtime safety and compile-time type inference on the client. This pattern prevents invalid data from reaching your database.

### Can I self-host Open-SEO without Docker?

Yes. Deploy to **Cloudflare Workers** using `wrangler publish` after configuring [`wrangler.toml`](https://github.com/every-app/open-seo/blob/main/wrangler.toml). The Workers path provides edge-scale distribution without container management. Both Docker and Workers deployments run identical pre-flight checks to validate environment readiness.

### What happens if required environment variables are missing?

The `runSelfhostPreflight()` function in [`src/lib/selfhost-preflight.ts`](https://github.com/every-app/open-seo/blob/main/src/lib/selfhost-preflight.ts) detects missing variables during startup and prevents the server from launching. When using Docker, [`docker-entrypoint.sh`](https://github.com/every-app/open-seo/blob/main/docker-entrypoint.sh) runs these checks automatically, ensuring the container fails fast with a clear error message rather than starting in a broken state.