---
title: Self-hosting
description: Run the service and its documentation from the same Vercel project.
sidebar:
  order: 8
  icon: server
---

## Run locally

```bash
git clone https://github.com/pc-style/x-md.git
cd x-md
bun install
bun run dev
```

The Vite server runs the landing page and local API handlers. Run `bun run docs:dev` separately for the Blume documentation server.

## Build and deploy

```bash
bun run build
vercel --prod
```

The build generates Blume docs, then the Vite landing page into `dist`. The included Vercel configuration serves `/docs`, the static assets, and the API rewrites together.

<FileTree>

- docs/
  - index.mdx
  - (api)/
    - posts.mdx
    - search.mdx
    - profiles.mdx
    - pagination.mdx
    - responses.mdx
    - reliability.mdx
    - meta.ts
  - agents.mdx
  - self-hosting.mdx
- blume.config.ts
- theme.css

</FileTree>

## Live search provider

Latest and Top try FxTwitter, then the custom-built live search provider, then an optional Firecrawl web fallback. Photos, Videos, and Users use the live provider directly and return `503` when it is not configured.

The provider is configured through `X_SEARCH_SESSIONS_JSON`; the expected shape is defined in `lib/xsearch.ts`. For local development, use the gitignored `accounts.local.json`. Treat its contents as secrets and keep them out of version control.

The provider budgets its own upstream capacity per 15-minute window, shared across post and user searches. Failed attempts and page walks count. Unhealthy entries are taken out of rotation in the current process and retried later; health is per instance.

## Shared counters

Attach Upstash Redis through the Vercel marketplace or CLI. The service automatically uses either environment-variable pair:

- `KV_REST_API_URL` and `KV_REST_API_TOKEN`
- `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`

These share per-IP and provider counters across instances. Without them, counters fall back to process memory. Live-provider checks fail closed when the counter store is unavailable, protecting the shared pool; the general request limiter still fails open. Redeploy after connecting environment variables.

## Historical statistics

PostHog is optional. Connect its Vercel Marketplace integration to the production environment, choose your data region, and review its billing terms. It supplies `NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN` and `NEXT_PUBLIC_POSTHOG_HOST`; the API reads these on the server only. Alternatively, set `POSTHOG_PROJECT_TOKEN` and `POSTHOG_HOST` (for example, `https://eu.i.posthog.com`). Redeploy after configuring them.

Production API functions send a `request_completed` event with the route category, method, HTTP status, duration in milliseconds, application-cache result, key-validation status, public/key access, and degraded-search flag. Use these for request volume by endpoint, error and `429` rates, latency percentiles, and application-cache hit rate. Delivery runs in the background with a three-second timeout; failures do not change API responses.

`request_completed` events contain no search text, URLs, client IPs, bearer tokens, or key labels. Verified keys use a pseudonymous ID derived from their internal ID and the PostHog project token; rotating that token changes the pseudonyms. Public requests have a new ID per event, so distinct-user counts and anonymous retention are not meaningful. Person profiles, geolocation enrichment, and session replay are not enabled by this instrumentation.

These statistics cover completed function responses, not requests served by Vercel's CDN, aborted requests, or platform errors before the handler runs. They are not billing-grade totals. Local development, previews, admin routes, and CORS preflights are excluded. Redis remains the source of truth for live limits and capacity; PostHog is only for historical analysis.

### Landing-page web analytics

Set `VITE_POSTHOG_KEY` to the project token and `VITE_POSTHOG_HOST` to its ingestion host or managed reverse proxy (this deployment uses `https://p.pcstyle.dev`). These are public build-time values, not personal API credentials. Rebuild after changing them. Capture requires a production build with `VERCEL_ENV=production`; local development and previews do not send events. The SDK's `ui_host` points to EU PostHog. In PostHog's Web Analytics settings, add your public site URL (for this deployment, `https://x.pcstyle.dev`).

The landing page sends pageviews, page leaves, `conversion_requested`, and `skill_install_command_copied`. Anonymous browser IDs persist in local storage to measure returning visitors; session and browser/device properties support Web Analytics. No person profiles are created. Event URLs contain only the landing-page origin and `/`, without query strings or fragments. Referrers, form contents, automatic clicks, errors, and recordings are excluded; traffic-source attribution and geographic reports are therefore limited. The proxy receives the browser's source IP, while event properties disable IP-based enrichment.

This covers the landing page only, not docs, converted posts, or admin. API events remain separate from browser visits. Existing Vercel Web Analytics on the public landing page is unchanged. The admin dashboard includes no analytics scripts.

## Optional configuration

| Variable | Purpose |
| --- | --- |
| `FIRECRAWL_API_KEY` | Conversion fallback and degraded Latest/Top search |
| `CONTEXT_DEV_API_KEY` | Additional post-conversion fallback |
| `CACHE_TTL_SECONDS` | Cache lifetime; default 3600 |
| `CACHE_DISABLED` | Disable application caching |
| `CACHE_PERSIST` | Control persistent application caching |

:::tip
FxTwitter is the primary conversion provider, followed by X syndication and configured fallbacks. Inspect `X-Source` when diagnosing a response.
:::
