perf: lazy load Remotion and PostHog to reduce initial bundle by ~355 KB
- Remotion Player + compositions (SchemaDemo, DashboardDemo, EcommerceDemo) replaced with React.lazy wrappers in routes/index.tsx — now split into separate chunks loaded only when the landing page sections are visible - posthog-js static import replaced with a dynamic load() wrapper in lib/posthog.ts; all exported functions remain fire-and-forget with the same API signature - subscription/index.tsx updated to use new setPeopleProperties() helper instead of the raw posthog instance Initial chunk: 1,082 KB → 727 KB (-33%), gzip 324 KB → 212 KB Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,24 +1,39 @@
|
||||
import posthog from "posthog-js";
|
||||
// PostHog — lazily loaded so it doesn't land in the initial bundle.
|
||||
// All exported functions are fire-and-forget; analytics loss on failure is acceptable.
|
||||
|
||||
let initialized = false;
|
||||
type PostHog = import("posthog-js").PostHog;
|
||||
|
||||
export function initPostHog() {
|
||||
const key = import.meta.env.VITE_POSTHOG_KEY;
|
||||
if (!key || initialized) return;
|
||||
let _ph: PostHog | null = null;
|
||||
let _loadPromise: Promise<PostHog> | null = null;
|
||||
let _initialized = false;
|
||||
|
||||
posthog.init(key, {
|
||||
api_host: "https://eu.i.posthog.com",
|
||||
person_profiles: "identified_only",
|
||||
capture_pageview: false,
|
||||
capture_pageleave: false,
|
||||
autocapture: false,
|
||||
session_recording: {
|
||||
maskAllInputs: false,
|
||||
maskInputOptions: { password: true },
|
||||
},
|
||||
function load(): Promise<PostHog> {
|
||||
if (_ph) return Promise.resolve(_ph);
|
||||
if (_loadPromise) return _loadPromise;
|
||||
_loadPromise = import("posthog-js").then((m) => {
|
||||
_ph = m.default;
|
||||
return _ph;
|
||||
});
|
||||
return _loadPromise;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
export function initPostHog(): void {
|
||||
const key = import.meta.env.VITE_POSTHOG_KEY;
|
||||
if (!key || _initialized) return;
|
||||
_initialized = true;
|
||||
load().then((ph) => {
|
||||
ph.init(key, {
|
||||
api_host: "https://eu.i.posthog.com",
|
||||
person_profiles: "identified_only",
|
||||
capture_pageview: false,
|
||||
capture_pageleave: false,
|
||||
autocapture: false,
|
||||
session_recording: {
|
||||
maskAllInputs: false,
|
||||
maskInputOptions: { password: true },
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function identifyUser(user: {
|
||||
@@ -26,26 +41,30 @@ export function identifyUser(user: {
|
||||
email: string;
|
||||
name: string;
|
||||
role: string;
|
||||
}) {
|
||||
posthog.identify(user.id, {
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
}): void {
|
||||
load().then((ph) => {
|
||||
ph.identify(user.id, {
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function resetUser() {
|
||||
posthog.reset();
|
||||
export function resetUser(): void {
|
||||
load().then((ph) => ph.reset());
|
||||
}
|
||||
|
||||
export function capture(event: string, properties?: Record<string, unknown>) {
|
||||
posthog.capture(event, properties);
|
||||
export function capture(event: string, properties?: Record<string, unknown>): void {
|
||||
load().then((ph) => ph.capture(event, properties));
|
||||
}
|
||||
|
||||
export function capturePageView(path: string) {
|
||||
posthog.capture("$pageview", {
|
||||
$current_url: window.location.origin + path,
|
||||
});
|
||||
export function capturePageView(path: string): void {
|
||||
load().then((ph) =>
|
||||
ph.capture("$pageview", { $current_url: window.location.origin + path }),
|
||||
);
|
||||
}
|
||||
|
||||
export { posthog };
|
||||
export function setPeopleProperties(properties: Record<string, unknown>): void {
|
||||
load().then((ph) => ph.people?.set(properties));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { lazy, Suspense, useEffect, useRef, useState } from "react";
|
||||
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||
import { api } from "@/lib/api-client";
|
||||
import { startAction } from "@/lib/faro";
|
||||
import { capture, posthog } from "@/lib/posthog";
|
||||
import { capture, setPeopleProperties } from "@/lib/posthog";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@sase/ui";
|
||||
import { Badge } from "@sase/ui";
|
||||
@@ -157,7 +157,7 @@ function SubscriptionPage() {
|
||||
// Set subscription properties on user in PostHog
|
||||
useEffect(() => {
|
||||
if (subscription) {
|
||||
posthog.people?.set({
|
||||
setPeopleProperties({
|
||||
subscription_status: subscription.status,
|
||||
subscription_plan: subscription.plan?.key,
|
||||
billing_period: subscription.billingPeriod,
|
||||
|
||||
@@ -24,11 +24,68 @@ import {
|
||||
Gift,
|
||||
Puzzle,
|
||||
} from "lucide-react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Player } from "@remotion/player";
|
||||
import { SchemaDemo } from "@/remotion/SchemaDemo";
|
||||
import { DashboardDemo } from "@/remotion/DashboardDemo";
|
||||
import { EcommerceDemo } from "@/remotion/EcommerceDemo";
|
||||
import { useState, useEffect, lazy, Suspense } from "react";
|
||||
|
||||
// Remotion — lazy loaded to keep initial bundle lean
|
||||
const RemotionSchemaPlayer = lazy(() =>
|
||||
Promise.all([import("@remotion/player"), import("@/remotion/SchemaDemo")]).then(
|
||||
([{ Player }, { SchemaDemo }]) => ({
|
||||
default: ({ isDark }: { isDark: boolean }) => (
|
||||
<Player
|
||||
component={SchemaDemo}
|
||||
compositionWidth={400}
|
||||
compositionHeight={300}
|
||||
durationInFrames={240}
|
||||
fps={30}
|
||||
loop
|
||||
autoPlay
|
||||
style={{ width: "100%" }}
|
||||
inputProps={{ isDark }}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const RemotionDashboardPlayer = lazy(() =>
|
||||
Promise.all([import("@remotion/player"), import("@/remotion/DashboardDemo")]).then(
|
||||
([{ Player }, { DashboardDemo }]) => ({
|
||||
default: ({ isDark }: { isDark: boolean }) => (
|
||||
<Player
|
||||
component={DashboardDemo}
|
||||
compositionWidth={960}
|
||||
compositionHeight={540}
|
||||
durationInFrames={450}
|
||||
fps={30}
|
||||
loop
|
||||
autoPlay
|
||||
style={{ width: "100%" }}
|
||||
inputProps={{ isDark }}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const RemotionEcommercePlayer = lazy(() =>
|
||||
Promise.all([import("@remotion/player"), import("@/remotion/EcommerceDemo")]).then(
|
||||
([{ Player }, { EcommerceDemo }]) => ({
|
||||
default: ({ isDark }: { isDark: boolean }) => (
|
||||
<Player
|
||||
component={EcommerceDemo}
|
||||
compositionWidth={960}
|
||||
compositionHeight={540}
|
||||
durationInFrames={420}
|
||||
fps={30}
|
||||
loop
|
||||
autoPlay
|
||||
style={{ width: "100%" }}
|
||||
inputProps={{ isDark }}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
),
|
||||
);
|
||||
import { getUserSettings, setUserSetting } from "@/lib/user-settings";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { usePageMeta } from "@/hooks/use-page-meta";
|
||||
@@ -119,17 +176,9 @@ const FEATURES = [
|
||||
],
|
||||
mockupTitle: "İnteraktif Şema — Motor Bölgesi",
|
||||
mockupContent: (isDark: boolean) => (
|
||||
<Player
|
||||
component={SchemaDemo}
|
||||
compositionWidth={400}
|
||||
compositionHeight={300}
|
||||
durationInFrames={240}
|
||||
fps={30}
|
||||
loop
|
||||
autoPlay
|
||||
style={{ width: "100%" }}
|
||||
inputProps={{ isDark }}
|
||||
/>
|
||||
<Suspense fallback={<div className="flex h-[300px] items-center justify-center text-muted-foreground text-sm">Yükleniyor…</div>}>
|
||||
<RemotionSchemaPlayer isDark={isDark} />
|
||||
</Suspense>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -1119,17 +1168,9 @@ function HomePage() {
|
||||
|
||||
<div className="relative mt-12">
|
||||
<BrowserFrame title="sase.tr/dashboard">
|
||||
<Player
|
||||
component={DashboardDemo}
|
||||
compositionWidth={960}
|
||||
compositionHeight={540}
|
||||
durationInFrames={450}
|
||||
fps={30}
|
||||
loop
|
||||
autoPlay
|
||||
style={{ width: "100%" }}
|
||||
inputProps={{ isDark }}
|
||||
/>
|
||||
<Suspense fallback={<div className="flex h-[540px] items-center justify-center text-muted-foreground text-sm">Yükleniyor…</div>}>
|
||||
<RemotionDashboardPlayer isDark={isDark} />
|
||||
</Suspense>
|
||||
</BrowserFrame>
|
||||
|
||||
{/* Floating labels */}
|
||||
@@ -1172,17 +1213,9 @@ function HomePage() {
|
||||
{/* Left — Video */}
|
||||
<div className="flex-1">
|
||||
<BrowserFrame title="otoyedekparca.co — Sase.tr Entegrasyonu">
|
||||
<Player
|
||||
component={EcommerceDemo}
|
||||
compositionWidth={960}
|
||||
compositionHeight={540}
|
||||
durationInFrames={420}
|
||||
fps={30}
|
||||
loop
|
||||
autoPlay
|
||||
style={{ width: "100%" }}
|
||||
inputProps={{ isDark }}
|
||||
/>
|
||||
<Suspense fallback={<div className="flex h-[540px] items-center justify-center text-muted-foreground text-sm">Yükleniyor…</div>}>
|
||||
<RemotionEcommercePlayer isDark={isDark} />
|
||||
</Suspense>
|
||||
</BrowserFrame>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user