Today Platform Web — Dev Docs
Workflow

Local development

Three dev modes — localhost-direct, local full-stack, and remote dev — plus proxy setup, env files, and troubleshooting.

Prerequisites

Development Modes

The web app supports two modes depending on which backend you connect to:

ModeBackendDomainProxy RulesUse Case
LocalFull-stack local serviceshttps://t.aiProxyman today-local-dev.jsBackend + frontend work
Remote DevRemote todayai.dev serviceshttps://todayai.devWhistle today-remote-dev.rules (recommended), Proxyman today-remote-dev.jsFrontend-only work

Quick Start (Remote Dev)

The fastest way to get started — no backend services needed:

pnpm install

# Copy the example and fill in credentials
cp apps/web/.env.development.local.example apps/web/.env.development.local
# Edit .env.development.local: uncomment the URL overrides and set credentials

pnpm dev

Set up wproxy with today-remote-dev.rules (see Proxy Setup), then browse https://todayai.dev or https://admin.todayai.dev.

Quick Start (Local Full-Stack)

Requires all backend services running locally (see today-cloud local development guide):

pnpm install

# Copy and fill credentials (leave URL overrides commented out)
cp apps/web/.env.development.local.example apps/web/.env.development.local

pnpm dev

Set up your local domain proxy (for example Proxyman with today-local-dev.js), then browse https://t.ai.

Localhost-direct dev mode (LOCALHOST_BFF)

pnpm dev serves the app directly at http://localhost:<port>no whistle or Proxyman proxy required. It picks the first free port in 4060-4069, so multiple worktrees each get their own port and run side by side.

This works because of the LOCALHOST_BFF flag, set to true in the committed apps/web/.env.development. With it on, the auth BFF strips the Domain attribute from the better-auth session cookie it relays, so the browser stores the cookie host-only for localhost instead of rejecting it for the .todayai.dev domain mismatch. Without that strip a direct-localhost login succeeds on the server but the browser drops the session cookie — which is the original reason a domain proxy was needed at all.

  • Just run pnpm dev and open the http://localhost:<port> URL it prints. Email-OTP login, the authenticated app, and chat/SSE all work directly.
  • LOCALHOST_BFF is server-only (no NEXT_PUBLIC_ prefix) and lives in .env.development, which Next loads only under NODE_ENV=development — a production build never sees it.
  • The whistle / Proxyman setup below is still available, and still needed for flows that must round-trip a real todayai.dev OAuth callback (e.g. Google social login) — but it is no longer required for everyday email-OTP dev.
  • Caveat: browser cookies are scoped by host, not port, so two worktrees on localhost:<different ports> share one cookie jar within a single browser profile — logging into one replaces the other's session there. Use a separate browser profile per worktree if you need both logged in at once.

See docs/plans/2026-05-22-localhost-bff-dev-mode.md for the design rationale.

Environment Variables

File Structure

Next.js loads env files in this order during next dev (NODE_ENV=development):

PriorityFileCommittedPurpose
1 (highest).env.development.localNoCredentials + optional URL overrides
2.env.developmentYesDefault URLs for local backend
3 (lowest).envYesShared defaults (if any)

.env.development (committed)

Default URLs pointing to the local backend:

NEXT_PUBLIC_OIDC_AUTHORITY=https://auth.t.ai
OIDC_INTERNAL_AUTHORITY=http://localhost:4010
NEXT_PUBLIC_API_URL=https://api.t.ai

.env.development.local (gitignored)

Credentials and optional URL overrides. Copy from .env.development.local.example:

# --- Credentials (always required) ---
NEXT_PUBLIC_OIDC_CLIENT_ID=replace-with-client-id
OIDC_CLIENT_SECRET=replace-with-client-secret

# --- URL overrides (uncomment to connect to dev backend) ---
# NEXT_PUBLIC_OIDC_AUTHORITY=https://auth.todayai.dev
# OIDC_INTERNAL_AUTHORITY=https://auth.todayai.dev
# NEXT_PUBLIC_API_URL=https://api.todayai.dev
  • Local mode: Leave URL overrides commented — defaults from .env.development apply.
  • Remote dev mode: Uncomment the URL overrides to point at todayai.dev.

Getting Credentials

From Vercel (remote dev):

cd apps/web
vercel link  # Link to todayai/today-web
vercel env pull .env.development.local --environment preview

Then remove extraneous variables and keep only the ones listed above.

From seed script (local):

cd ../today-cloud/services/auth-service && pnpm db:seed-clients

Convenience Scripts

pnpm dev          # Use existing .env.development.local as-is
pnpm dev:local    # Delete .env.development.local → use local backend defaults
pnpm dev:remote   # Ensure .env.development.local exists (from example) → use remote

OAuth login requires domain-aligned cookies. Direct localhost access breaks social login (Google/GitHub) because cookie domains don't match. A domain proxy maps production-like domains to local dev servers.

todayai.dev        → localhost:4060 (local web)
admin.todayai.dev  → localhost:4061 (local admin)
auth.todayai.dev   → remote (direct)
api.todayai.dev    → remote (direct)

Use:

  • dev-tools/whistle/today-remote-dev.rules
  • dev-tools/whistle/README.md

Quick steps:

  1. Install whistle (npm i -g whistle) and start it (w2 start)
  2. Import dev-tools/whistle/today-remote-dev.rules
  3. Set system/browser proxy to 127.0.0.1:8899
  4. Install and trust whistle CA (w2 ca)
  5. Browse https://todayai.dev or https://admin.todayai.dev

Proxyman Remote Dev Mode (alternative)

todayai.dev        → localhost:4060 (Proxyman)
admin.todayai.dev  → localhost:4061 (Proxyman)
auth.todayai.dev   → remote (direct)
api.todayai.dev    → remote (direct)

Setup:

  1. In Proxyman, create a new Script
    • Name: Today AI Remote Dev
    • Matching Rule: Regex
    • URL Pattern: ^https?://(admin\.)?todayai\.dev($|/.*)
  2. Paste the contents of dev-tools/proxyman/today-remote-dev.js
  3. Enable Run Script on Request
  4. Add todayai.dev and admin.todayai.dev to SSL Proxying List
  5. Browse https://todayai.dev or https://admin.todayai.dev

Local Full-Stack Mode

All external-facing domains are proxied to local services.

t.ai               → localhost:4060 (web)
auth.t.ai          → localhost:4010 (auth)
api.t.ai           → localhost:4020 (api gateway)

Setup:

  1. In Proxyman, create a new Script
    • Name: Today AI Local Dev
    • Matching Rule: Regex
    • URL Pattern: ^https?://([a-z]+\.)*t\.ai($|/.*)
  2. Paste the contents of today-cloud/dev-tools/proxyman/today-local-dev.js
  3. Enable Run Script on Request
  4. Add t.ai and *.t.ai to SSL Proxying List
  5. Browse https://t.ai

Only enable one Proxyman script at a time.

Architecture

Request Flow (Remote Dev)

Browser (https://todayai.dev)
  |
  +-- todayai.dev/*  ---- domain proxy (wproxy/Proxyman) ----> localhost:4060 (Next.js)
  |                                         |
  |                                         +-- /api/auth/* (BFF proxy)
  |                                         |       |
  |                                         |       +--> auth.todayai.dev (remote)
  |                                         |
  |                                         +-- SSR pages (local render)
  |
  +-- auth.todayai.dev/* --- direct -----> remote auth service
  |                                        (Google OAuth callback lands here)
  |
  +-- api.todayai.dev/*  --- direct -----> remote API gateway

Why a domain proxy is required for OAuth

Social login (Google/GitHub) uses this flow:

  1. Frontend on todayai.dev initiates login via BFF
  2. BFF proxies to auth.todayai.dev, which redirects to Google
  3. Google redirects back to auth.todayai.dev/api/auth/callback/google
  4. Auth server sets session cookie on domain .todayai.dev
  5. Auth server redirects back to todayai.dev
  6. Browser sends .todayai.dev cookie on subsequent requests

If you access via localhost:4060 instead, step 6 breaks — the browser won't send .todayai.dev cookies to localhost. A domain proxy ensures the browser sees todayai.dev throughout, keeping cookies aligned.

BFF Proxy

The web app uses a Backend-for-Frontend pattern. All auth requests go through /api/auth/* route handlers that proxy to the auth server:

Browser  -->  /api/auth/get-session  -->  auth.todayai.dev/api/auth/get-session
              (Next.js API route)         (via OIDC_INTERNAL_AUTHORITY)

OIDC_INTERNAL_AUTHORITY controls where the BFF proxy sends server-side requests:

  • Local mode: http://localhost:4010 (direct, bypasses the proxy)
  • Remote dev mode: https://auth.todayai.dev (direct to remote)

Troubleshooting

Login Redirects to Localhost and Fails

You're accessing localhost:4060 directly instead of through the configured proxy. Use https://todayai.dev (remote dev) or https://t.ai (local).

"Invalid origin" on Login

The auth server's BETTER_AUTH_TRUSTED_ORIGINS doesn't include your origin. For remote dev, https://todayai.dev should already be listed. For local, ensure https://t.ai is in the auth service's trusted origins.

"OIDC client_id not configured"

.env.development.local is missing or doesn't have NEXT_PUBLIC_OIDC_CLIENT_ID. Copy from the example and fill in credentials.

502 on Auth API Calls

The BFF proxy can't reach the auth server. Check:

  • Remote dev: OIDC_INTERNAL_AUTHORITY=https://auth.todayai.dev in .env.development.local
  • Local: auth service running on port 4010, OIDC_INTERNAL_AUTHORITY=http://localhost:4010

"invalid_client" Error

The OIDC client ID/secret are wrong or the client doesn't exist in the auth server's database.

  • Remote dev: Pull credentials from Vercel (vercel env pull)
  • Local: Run pnpm db:seed-clients in the auth service

Stuck on Login Redirect

Clear OIDC state from the browser:

  1. DevTools → Application → Local Storage
  2. Delete all keys starting with oidc.
  3. Refresh the page

CORS Errors

Ensure the auth server has your origin in BETTER_AUTH_TRUSTED_ORIGINS.

Dependent Services (Local Mode)

ServicePortPurpose
auth-service4010Authentication
api-gateway4020API Gateway
agent-service4030AI Agent

Start them before the web app:

# In today-cloud repo
cd services/auth-service && pnpm dev    # Terminal 1
cd services/api && pnpm dev             # Terminal 2
cd services/agent-service && pnpm dev   # Terminal 3

# In today-platform-web repo
pnpm dev                                # Terminal 4

On this page