️(frontend) load PostHog dynamically

posthog-js was bundled in the main chunk. Loading it dynamically saves
200ko on the initial load and around 4s of loading time on slow 4G
connections.

PostHog, and analytics in general, should not block the initial
rendering. The risk is that some feature-flagged features may not be
instantaneously available to the user, but for most users on
high-speed connections this will be imperceptible.

Other places in the code rely on a direct import from posthog, I only
verified the delay import of posthog for the home page, and did
not for the /room route. I'm pretty sure there is room for
improvements on other parts of the app.
This commit is contained in:
lebaudantoine
2026-05-26 19:14:34 +02:00
committed by aleb_the_flash
parent 04ec967a99
commit c1d30f6923
8 changed files with 62 additions and 47 deletions
@@ -1,17 +1,28 @@
import { useEffect } from 'react'
import { useLocation } from 'wouter'
import posthog from 'posthog-js'
import { ApiUser } from '@/features/auth/api/ApiUser'
import { type PostHog } from 'posthog-js'
import { type ApiUser } from '@/features/auth/api/ApiUser'
import { useUser } from '@/features/auth/api/useUser'
export const startAnalyticsSession = (data: ApiUser) => {
if (posthog._isIdentified()) return
const { id, email } = data
posthog.identify(id, { email })
let posthog: PostHog | null = null
const getPosthog = async () => {
if (!posthog) posthog = (await import('posthog-js')).default
return posthog
}
export const terminateAnalyticsSession = () => {
if (!posthog._isIdentified()) return
posthog.reset()
export const startAnalyticsSession = (data: ApiUser) => {
getPosthog().then((ph) => {
if (ph._isIdentified()) return
const { id, email } = data
ph.identify(id, { email })
})
}
export const terminateAnalyticsSession = async () => {
const ph = await getPosthog()
if (!ph._isIdentified()) return
ph.reset()
}
export type useAnalyticsProps = {
@@ -22,18 +33,26 @@ export type useAnalyticsProps = {
export const useAnalytics = ({ id, host, isDisabled }: useAnalyticsProps) => {
const [location] = useLocation()
const { user } = useUser()
useEffect(() => {
if (!id || !host || isDisabled) return
if (posthog.__loaded) return
posthog.init(id, {
api_host: host,
person_profiles: 'always',
getPosthog().then((ph) => {
if (ph.__loaded) return
ph.init(id, { api_host: host, person_profiles: 'always' })
})
}, [id, host, isDisabled])
useEffect(() => {
if (!user) return
startAnalyticsSession(user)
}, [user])
// From PostHog tutorial on PageView tracking in a Single Page Application (SPA) context.
useEffect(() => {
posthog.capture('$pageview')
getPosthog().then((ph) => {
ph.capture('$pageview')
})
}, [location])
return null
+1 -24
View File
@@ -2,16 +2,7 @@ import { useQuery } from '@tanstack/react-query'
import { keys } from '@/api/queryKeys'
import { fetchUser } from './fetchUser'
import { type ApiUser } from './ApiUser'
import { useEffect, useMemo } from 'react'
import {
startAnalyticsSession,
terminateAnalyticsSession,
} from '@/features/analytics/hooks/useAnalytics'
import {
initializeSupportSession,
terminateSupportSession,
} from '@/features/support/hooks/useSupport'
import { logoutUrl } from '../utils/logoutUrl'
import { useMemo } from 'react'
import { useConfig } from '@/api/useConfig'
/**
@@ -45,19 +36,6 @@ export const useUser = (
enabled: !isConfigLoading,
})
useEffect(() => {
if (query?.data) {
startAnalyticsSession(query.data)
initializeSupportSession(query.data)
}
}, [query.data])
const logout = () => {
terminateAnalyticsSession()
terminateSupportSession()
window.location.href = logoutUrl()
}
const isLoggedIn =
query.status === 'success' ? query.data !== false : undefined
const isLoggedOut = isLoggedIn === false
@@ -67,6 +45,5 @@ export const useUser = (
user: isLoggedOut ? undefined : (query.data as ApiUser | undefined),
isLoggedIn,
isLoading: query.isLoading,
logout,
}
}
@@ -0,0 +1,13 @@
import { apiUrl } from '@/api/apiUrl'
import { terminateSupportSession } from '@/features/support/hooks/useSupport'
import { terminateAnalyticsSession } from '@/features/analytics/hooks/useAnalytics'
const logoutUrl = () => {
return apiUrl('/logout')
}
export const logout = async () => {
await terminateAnalyticsSession()
terminateSupportSession()
window.location.href = logoutUrl()
}
@@ -1,5 +0,0 @@
import { apiUrl } from '@/api/apiUrl'
export const logoutUrl = () => {
return apiUrl('/logout')
}
@@ -3,12 +3,13 @@ import { useLanguageLabels } from '@/i18n/useLanguageLabels'
import { A, Badge, Dialog, type DialogProps, Field, H, P } from '@/primitives'
import { useUser } from '@/features/auth/api/useUser'
import { LoginButton } from '@/components/LoginButton'
import { logout } from '@/features/auth/utils/logout'
export type SettingsDialogProps = Pick<DialogProps, 'isOpen' | 'onOpenChange'>
export const SettingsDialog = (props: SettingsDialogProps) => {
const { t, i18n } = useTranslation('settings')
const { user, isLoggedIn, logout } = useUser()
const { user, isLoggedIn } = useUser()
const { languagesList, currentLanguage } = useLanguageLabels()
const userDisplay =
user?.full_name && user?.email
@@ -9,6 +9,7 @@ import { useState } from 'react'
import { LoginButton } from '@/components/LoginButton'
import { useRenameParticipant } from '@/features/rooms/api/renameParticipant'
import { saveUsername } from '@/stores/userChoices'
import { logout } from '@/features/auth/utils/logout'
export type AccountTabProps = Pick<DialogProps, 'onOpenChange'> &
Pick<TabPanelProps, 'id'>
@@ -16,7 +17,7 @@ export type AccountTabProps = Pick<DialogProps, 'onOpenChange'> &
export const AccountTab = ({ id, onOpenChange }: AccountTabProps) => {
const { t } = useTranslation('settings')
const room = useRoomContext()
const { user, isLoggedIn, logout } = useUser()
const { user, isLoggedIn } = useUser()
const { renameParticipant } = useRenameParticipant()
@@ -1,6 +1,7 @@
import { useEffect } from 'react'
import { Crisp } from 'crisp-sdk-web'
import { ApiUser } from '@/features/auth/api/ApiUser'
import { type ApiUser } from '@/features/auth/api/ApiUser'
import { useUser } from '@/features/auth/api/useUser'
import { useConfig } from '@/api/useConfig'
export const initializeSupportSession = (user: ApiUser) => {
@@ -23,12 +24,19 @@ export type useSupportProps = {
// Configure Crisp chat for real-time support across all pages.
export const useSupport = ({ id, isDisabled }: useSupportProps) => {
const { user } = useUser()
useEffect(() => {
if (!id || Crisp.isCrispInjected() || isDisabled) return
Crisp.configure(id)
Crisp.setHideOnMobile(true)
}, [id, isDisabled])
useEffect(() => {
if (!user) return
initializeSupportSession(user)
}, [user])
return null
}
+2 -1
View File
@@ -13,6 +13,7 @@ import { LoginButton } from '@/components/LoginButton'
import { VisualOnlyTooltip } from '@/primitives/VisualOnlyTooltip'
import { useLoginHint } from '@/hooks/useLoginHint'
import { logout } from '@/features/auth/utils/logout'
const Logo = () => (
<img
@@ -90,7 +91,7 @@ export const Header = () => {
const isAccessibility = useMatchesRoute('accessibility')
const isTermsOfService = useMatchesRoute('termsOfService')
const isRoom = useMatchesRoute('room')
const { user, isLoggedIn, logout } = useUser()
const { user, isLoggedIn } = useUser()
const userLabel = user?.full_name || user?.email
const loggedInTooltip = t('loggedInUserTooltip')
const loggedInAriaLabel = userLabel