PostHog.com site architecture
Contents
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
:
When Gatsby loads a page, it passes:
element
– The page's React componentlocation
– 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:
Each window object contains:
element
– The React component to renderposition
– X/Y coordinatessize
– Width/height dimensionszIndex
– Window stacking orderminimized
– Minimized statepath
– Route pathappSettings
– Window-specific configuration
Core functions
Key window management functions include:
addWindow()
– Creates and adds new windows to statecloseWindow()
– Removes windows from statebringToFront()
– Updates z-index for window focusminimizeWindow()
– Sets minimized stateupdateWindow()
– 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:
- New window – If
newWindow: true
is passed in location state, or no existing window matches the path - Focus existing – If a window with the same path already exists, bring it to the front instead of creating a duplicate
- 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:
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:
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
- Creation – New
AppWindow
object added to state - Mounting – Component mounts with entrance animation
- Interaction – User can drag, resize, minimize, close
- 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
/
orCmd+K
– Open search?
– Open chat
Appearance
,
– Display options.
– Keyboard shortcuts\
– Toggle theme|
– Cycle wallpapers
Window control
Shift + Arrow keys
– Window snappingShift + W
– Close focused windowShift + 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:
- Test window creation – Ensure new pages create windows properly
- Check positioning – Verify windows open in expected locations
- Test interactions – Drag, resize, minimize, close functionality
- Verify animations – Smooth entrance and exit animations
- 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.