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

- App Provider (`src/context/App.tsx`) – Core state management and window system
- Wrapper (`src/components/Wrapper/index.tsx`) – Desktop layout and window rendering
- AppWindow (`src/components/AppWindow/index.tsx`) – Individual window state management
- Desktop (`src/components/Desktop/index.tsx`) – Desktop environment with wallpapers and icons

### 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:

- `element` – The page's React component
- `location` – Current route information

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:

- `element` – The React component to render
- `position` – X/Y coordinates
- `size` – Width/height dimensions
- `zIndex` – Window stacking order
- `minimized` – Minimized state
- `path` – Route path
- `appSettings` – Window-specific configuration

#### Core functions

Key window management functions include:

- `addWindow()` – Creates and adds new windows to state
- `closeWindow()` – Removes windows from state
- `bringToFront()` – Updates z-index for window focus
- `minimizeWindow()` – Sets minimized state
- `updateWindow()` – Updates position, size, and other properties

#### 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

- size.min/max – Minimum and maximum window dimensions
- size.fixed – Whether window can be resized
- size.autoHeight – Auto-adjust height to content
- position.center – Center window on screen
- position.getPositionDefaults – Custom positioning function

### 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:

- Desktop background and icons
- Taskbar at the top
- All active windows with animations

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

- Dragging – Windows can be dragged around the desktop
- Resizing – Resize handles on window borders
- Snapping – Windows snap to screen edges
- Minimizing – Windows minimize to taskbar
- Focus management – Click to bring windows to front
- Chrome – Window controls (close, minimize, maximize buttons)

#### 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`:

- posthog – Full desktop OS experience with windows
- boring – Traditional website navigation (used on mobile or when explicitly toggled)

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

- `/` or `Cmd+K` – Open search
- `?` – Open chat

Appearance

- `,` – Display options
- `.` – Keyboard shortcuts
- `\` – Toggle theme
- `|` – Cycle wallpapers

Window control

- `Shift + Arrow keys` – Window snapping
- `Shift + W` – Close focused window
- `Shift + X` – Close all windows

### SEO compatibility

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

- Pages are statically generated at build time
- Each route has proper HTML structure, canonical tags, and metadata
- Search engines crawl normal static files
- Client-side windowing does not affect crawling

### 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

- Windows not appearing – Check `appSettings` configuration for the route
- Positioning issues – Verify `getPositionDefaults` logic
- Animation problems – Check Framer Motion configurations in AppWindow
- State sync issues – Use React DevTools to inspect App Provider state

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.