PostHog Handbook Library / Engineering

891 words. Estimated reading time: 4 min.

PostHog.com site architecture

PostHog.com doesn’t behave like a normal website. Instead, it runs inside a desktop-style environment where every page is a draggable window. This guide explains how that system works under the hood.

Core architecture

PostHog.com runs on Gatsby with a custom windowing system built using React context providers. The entire site operates inside a desktop-like environment where traditional page navigation is replaced by window management.

At a high level, every page is wrapped in the App Provider, which manages global state and window logic. The Wrapper renders the desktop interface, and each page is displayed inside an AppWindow component on the Desktop.

Key components

How pages become windows

Every page in the site is wrapped using Gatsby's wrapPageElement API in gatsby-browser.tsx:

export const wrapPageElement = ({ element, props: { location } }) => {
    return (
    )
}

When Gatsby loads a page, it passes:

These get passed to the App Provider, which converts them into windows.

The App Provider system

Located at src/context/App.tsx, the App Provider is the core of our windowing system.

Window state management

The App Provider maintains an array of active windows in state:

const [windows, setWindows] = useState<AppWindow[]>([])

Each window object contains:

Core functions

Key window management functions include:

Window routing behavior

The App Provider decides whether to create, focus, or replace a window based on navigation state:

  1. New window – If newWindow: true is passed in location state, or no existing window matches the path
  2. Focus existing – If a window with the same path already exists, bring it to the front instead of creating a duplicate
  3. Replace – For standard navigation without newWindow: true, replace the content of the focused window

This prevents duplicate windows for the same route while still allowing intentional multi-window behavior.

App settings configuration

Window behavior is controlled by the appSettings object in src/context/App.tsx. Each route can have custom settings:

const appSettings: AppSettings = {
    '/': {
        size: {
            min: { width: 700, height: 500 },
            max: { width: 800, height: 1000 },
            fixed: false,
        },
        position: {
            center: true,
            getPositionDefaults: (size, windows, getDesktopCenterPosition) => {
                // Custom positioning logic
                // Currently only offsets the homepage window
                // so the default background is always fully visible
            },
        },
    },
    // More route configurations...
}

Configuration options

The Wrapper component

src/components/Wrapper/index.tsx handles the actual desktop rendering:

export default function Wrapper() {
    const { windows, constraintsRef } = useApp()

    return (
        <div className="fixed inset-0 size-full flex flex-col">
            <div ref={constraintsRef} className="flex-grow relative">
                    {windows.map((item) => (
                    ))}
            </div>
        </div>
    )
}

It renders:

It also provides drag constraints for window movement via constraintsRef.

Window implementation

Individual windows are implemented in src/components/AppWindow/index.tsx using Framer Motion for animations and drag interactions. Each window is wrapped in a Window Provider so that child components can access the current window object via the useWindow hook.

Key features

Window lifecycle

  1. Creation – New AppWindow object added to state
  2. Mounting – Component mounts with entrance animation
  3. Interaction – User can drag, resize, minimize, close
  4. Unmounting – Exit animation before removal from state

Experience modes

The site supports two experience modes controlled by siteSettings.experience:

During development you can manually force boring mode by setting siteSettings.experience = 'boring'. This is useful for debugging.

Keyboard shortcuts

Global keyboard shortcuts are handled in the App Provider:

Navigation and search

Appearance

Window control

SEO compatibility

Despite the desktop interface, the site maintains full SEO compatibility:

Development workflow

When working on the windowing system:

  1. Test window creation – Ensure new pages create windows properly
  2. Check positioning – Verify windows open in expected locations
  3. Test interactions – Drag, resize, minimize, close functionality
  4. Verify animations – Smooth entrance and exit animations
  5. Mobile compatibility – Ensure fallback to boring mode works

Common debugging

This architecture allows PostHog.com to feel like a desktop operating system while maintaining the benefits of a static website for performance and SEO.

Canonical URL: https://posthog.com/handbook/engineering/posthog-com/technical-architecture

GitHub source: contents/handbook/engineering/posthog-com/technical-architecture.md

Content hash: 78fb89c2505ad49c

Static reader notes
  • MDX_COMPONENT_STATIC_ADAPTER: Adapted interactive MDX components for static reading: AnimatePresence, AppWindow, Desktop, Provider, TaskBarMenu, Wrapper.