Documentation Index Fetch the complete documentation index at: https://mintlify.com/egeuysall/shipr/llms.txt
Use this file to discover all available pages before exploring further.
Shipr’s provider stack wraps the entire application to provide theming, analytics, authentication, database access, and UI utilities. The order of providers is critical for proper functionality.
Provider Stack Overview
All providers are configured in src/app/layout.tsx:106-149:
< html lang = "en" suppressHydrationWarning >
< head >
< OrganizationJsonLd />
< WebSiteJsonLd />
</ head >
< body >
< ThemeProvider
attribute = "class"
defaultTheme = "system"
enableSystem
disableTransitionOnChange
>
< PostHogProvider >
< ClerkProviderWrapper >
< PostHogIdentify />
< Suspense fallback = { null } >
< PostHogPageview />
</ Suspense >
< TooltipProvider >
< ConvexClientProvider >
{ children }
</ ConvexClientProvider >
</ TooltipProvider >
</ ClerkProviderWrapper >
</ PostHogProvider >
< Toaster />
</ ThemeProvider >
< Analytics />
< SpeedInsights />
</ body >
</ html >
Provider Hierarchy
ThemeProvider (next-themes)
└─ PostHogProvider (PostHog analytics)
└─ ClerkProviderWrapper (Clerk auth)
├─ PostHogIdentify (links Clerk user to PostHog)
├─ PostHogPageview (tracks route changes)
└─ TooltipProvider (shadcn/ui)
└─ ConvexClientProvider (Convex + Clerk integration)
└─ {children} (Your app content)
ThemeProvider
Package: next-themes
Location: src/components/theme-provider.tsx
Purpose: Light/dark/system theme management
Configuration
import { ThemeProvider as NextThemesProvider } from "next-themes" ;
import { type ThemeProviderProps } from "next-themes/dist/types" ;
export function ThemeProvider ({ children , ... props } : ThemeProviderProps ) {
return < NextThemesProvider { ... props } > { children } </ NextThemesProvider > ;
}
Props in layout.tsx:
< ThemeProvider
attribute = "class" // Use class="dark" for dark mode
defaultTheme = "system" // Default to system preference
enableSystem // Allow system theme detection
disableTransitionOnChange // Prevent FOUC on theme switch
>
Usage in Components
import { useTheme } from "next-themes" ;
export function ThemeToggle () {
const { theme , setTheme } = useTheme ();
return (
< button onClick = { () => setTheme ( theme === "dark" ? "light" : "dark" ) } >
Toggle theme
</ button >
);
}
Why it’s first: All child components need access to theme state, including other providers.
PostHogProvider
Package: posthog-js, posthog-js/react
Location: src/components/posthog-provider.tsx
Purpose: Product analytics and feature flags
Configuration
"use client" ;
import posthog from "posthog-js" ;
import { PostHogProvider as PHProvider } from "posthog-js/react" ;
if ( typeof window !== "undefined" ) {
posthog . init ( process . env . NEXT_PUBLIC_POSTHOG_KEY ! , {
api_host: process . env . NEXT_PUBLIC_POSTHOG_HOST ,
person_profiles: "identified_only" ,
capture_pageviews: false , // We use PostHogPageview component
capture_pageleave: true ,
});
}
export function PostHogProvider ({ children } : { children : React . ReactNode }) {
return < PHProvider client = { posthog } > { children } </ PHProvider > ;
}
PostHogIdentify Component
Location: src/components/posthog-identify.tsx
Purpose: Link Clerk user identity to PostHog
"use client" ;
import { useUser } from "@clerk/nextjs" ;
import { usePostHog } from "posthog-js/react" ;
import { useEffect } from "react" ;
export function PostHogIdentify () {
const { user , isLoaded } = useUser ();
const posthog = usePostHog ();
useEffect (() => {
if ( isLoaded && user ) {
posthog . identify ( user . id , {
email: user . primaryEmailAddress ?. emailAddress ,
name: user . fullName ,
});
} else if ( isLoaded && ! user ) {
posthog . reset (); // Clear identity on sign out
}
}, [ user , isLoaded , posthog ]);
return null ;
}
PostHogPageview Component
Location: src/components/posthog-pageview.tsx
Purpose: Track route changes in App Router
"use client" ;
import { usePathname , useSearchParams } from "next/navigation" ;
import { usePostHog } from "posthog-js/react" ;
import { useEffect } from "react" ;
export function PostHogPageview () {
const pathname = usePathname ();
const searchParams = useSearchParams ();
const posthog = usePostHog ();
useEffect (() => {
if ( pathname ) {
let url = window . origin + pathname ;
if ( searchParams . toString ()) {
url = url + `? ${ searchParams . toString () } ` ;
}
posthog . capture ( "$pageview" , { $current_url: url });
}
}, [ pathname , searchParams , posthog ]);
return null ;
}
Note: Wrapped in <Suspense> to prevent blocking during static generation.
Why PostHog is second: It needs to initialize before Clerk so analytics are ready when user authentication happens.
ClerkProviderWrapper
Package: @clerk/nextjs
Location: src/components/clerk-provider-wrapper.tsx
Purpose: User authentication and session management
Configuration
"use client" ;
import { ClerkProvider } from "@clerk/nextjs" ;
import { dark } from "@clerk/themes" ;
import { useTheme } from "next-themes" ;
export function ClerkProviderWrapper ({ children } : { children : React . ReactNode }) {
const { resolvedTheme } = useTheme ();
return (
< ClerkProvider
appearance = { {
baseTheme: resolvedTheme === "dark" ? dark : undefined ,
} }
>
{ children }
</ ClerkProvider >
);
}
Key features:
Theme adaptation: Clerk UI matches app theme (light/dark)
Auto-sync: Uses useTheme() from ThemeProvider
Session management: Provides useAuth(), useUser(), useSession() hooks
Environment Variables
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = pk_test_...
CLERK_SECRET_KEY = sk_test_...
CLERK_JWT_ISSUER_DOMAIN = clerk.your-domain.com
# Optional: Customize Clerk URLs
NEXT_PUBLIC_CLERK_SIGN_IN_URL = /sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL = /sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL = /dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL = /onboarding
Usage in Components
import { useAuth , useUser } from "@clerk/nextjs" ;
export function UserProfile () {
const { isLoaded , userId } = useAuth ();
const { user } = useUser ();
if ( ! isLoaded ) return < LoadingSpinner /> ;
if ( ! userId ) return < SignInButton /> ;
return < div > Welcome, { user ?. fullName } ! </ div > ;
}
Why Clerk is third: It depends on ThemeProvider for theme state.
ConvexClientProvider
Package: convex/react, convex/react-clerk
Location: src/lib/convex-client-provider.tsx
Purpose: Realtime database with Clerk authentication
Configuration
"use client" ;
import { ReactNode } from "react" ;
import { ConvexReactClient } from "convex/react" ;
import { ConvexProviderWithClerk } from "convex/react-clerk" ;
import { useAuth } from "@clerk/nextjs" ;
if ( ! process . env . NEXT_PUBLIC_CONVEX_URL ) {
throw new Error ( "Missing NEXT_PUBLIC_CONVEX_URL in your .env file" );
}
const convex = new ConvexReactClient ( process . env . NEXT_PUBLIC_CONVEX_URL );
export function ConvexClientProvider ({ children } : { children : ReactNode }) {
return (
< ConvexProviderWithClerk client = { convex } useAuth = { useAuth } >
{ children }
</ ConvexProviderWithClerk >
);
}
Clerk + Convex Integration
Convex uses Clerk’s JWT tokens for authentication. The integration requires:
1. Convex auth config (convex/auth.config.ts:1-15):
import { AuthConfig } from "convex/server" ;
export default {
providers: [
{
domain: process . env . CLERK_JWT_ISSUER_DOMAIN ,
applicationID: "convex" ,
},
] ,
} satisfies AuthConfig ;
2. ConvexProviderWithClerk uses useAuth hook to:
Get JWT token from Clerk session
Send token with every Convex query/mutation
Enable ctx.auth.getUserIdentity() in Convex functions
Usage in Components
import { useQuery , useMutation } from "convex/react" ;
import { api } from "@convex/_generated/api" ;
export function Dashboard () {
const user = useQuery ( api . users . getCurrentUser );
const updateUser = useMutation ( api . users . createOrUpdateUser );
if ( ! user ) return < LoadingSpinner /> ;
return (
< div >
< h1 > Welcome, { user . name } ! </ h1 >
< button onClick = { () => updateUser ({ name: "New Name" }) } >
Update Name
</ button >
</ div >
);
}
Why Convex is last: It depends on Clerk’s useAuth() hook for authentication.
Package: @radix-ui/react-tooltip (via shadcn/ui)
Location: src/components/ui/tooltip.tsx
Purpose: Shared tooltip context for UI components
Configuration
import { TooltipProvider } from "@/components/ui/tooltip" ;
< TooltipProvider >
{ children }
</ TooltipProvider >
Default delay: 200ms (configurable via delayDuration prop)
Usage in Components
import {
Tooltip ,
TooltipContent ,
TooltipTrigger ,
} from "@/components/ui/tooltip" ;
export function HelpButton () {
return (
< Tooltip >
< TooltipTrigger asChild >
< button > Help </ button >
</ TooltipTrigger >
< TooltipContent >
< p > Click for help </ p >
</ TooltipContent >
</ Tooltip >
);
}
Additional Components
Toaster
Package: sonner
Location: src/components/ui/sonner.tsx
Purpose: Toast notifications
import { Toaster } from "@/components/ui/sonner" ;
< ThemeProvider >
{ children }
< Toaster /> { /* Rendered outside main provider tree */ }
</ ThemeProvider >
Usage:
import { toast } from "sonner" ;
export function SaveButton () {
const handleSave = async () => {
try {
await saveData ();
toast . success ( "Saved successfully!" );
} catch ( error ) {
toast . error ( "Failed to save" );
}
};
return < button onClick = { handleSave } > Save </ button > ;
}
Analytics & SpeedInsights
Packages: @vercel/analytics, @vercel/speed-insights
Location: src/app/layout.tsx:145-146
Purpose: Vercel web analytics and performance monitoring
import { Analytics } from "@vercel/analytics/react" ;
import { SpeedInsights } from "@vercel/speed-insights/next" ;
< body >
{ /* ... providers ... */ }
< Analytics />
< SpeedInsights />
</ body >
Automatic tracking:
Page views
Web vitals (LCP, FID, CLS, TTFB, FCP)
No configuration required on Vercel
Provider Dependency Graph
Key dependencies:
ClerkProviderWrapper depends on ThemeProvider for theme state
PostHogIdentify depends on ClerkProviderWrapper for user data
ConvexClientProvider depends on ClerkProviderWrapper for useAuth() hook
Environment Variables
All required environment variables for providers:
# Clerk (required)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = pk_test_...
CLERK_SECRET_KEY = sk_test_...
CLERK_JWT_ISSUER_DOMAIN = clerk.your-domain.com
# Convex (required)
NEXT_PUBLIC_CONVEX_URL = https://your-deployment.convex.cloud
# PostHog (optional)
NEXT_PUBLIC_POSTHOG_KEY = phc_...
NEXT_PUBLIC_POSTHOG_HOST = https://app.posthog.com
Common Patterns
Accessing User Data
import { useUser } from "@clerk/nextjs" ; // Clerk user
import { useQuery } from "convex/react" ;
import { api } from "@convex/_generated/api" ;
export function UserDisplay () {
const { user : clerkUser } = useUser (); // Clerk session
const convexUser = useQuery ( api . users . getCurrentUser ); // Convex DB record
return (
< div >
< p > Email: { clerkUser ?. primaryEmailAddress ?. emailAddress } </ p >
< p > Plan: { convexUser ?. plan } </ p >
</ div >
);
}
Theme-aware Styling
import { useTheme } from "next-themes" ;
export function ThemedComponent () {
const { resolvedTheme } = useTheme ();
return (
< div className = { resolvedTheme === "dark" ? "bg-black" : "bg-white" } >
Content
</ div >
);
}
Analytics Tracking
import { usePostHog } from "posthog-js/react" ;
export function UpgradeButton () {
const posthog = usePostHog ();
const handleUpgrade = () => {
posthog . capture ( "upgrade_clicked" , {
plan: "pro" ,
source: "dashboard" ,
});
// ... upgrade logic
};
return < button onClick = { handleUpgrade } > Upgrade </ button > ;
}
Troubleshooting
Hydration Errors
Problem: Theme flicker or hydration mismatch
Solution: Add suppressHydrationWarning to <html> tag
< html lang = "en" suppressHydrationWarning >
Clerk Theme Not Updating
Problem: Clerk UI doesn’t match app theme
Solution: Ensure ClerkProviderWrapper uses resolvedTheme not theme
const { resolvedTheme } = useTheme (); // ✅ Use resolvedTheme
const { theme } = useTheme (); // ❌ May be "system"
PostHog Not Tracking
Problem: Events not appearing in PostHog
Solution: Check environment variables and init config
// Verify in browser console:
console . log ( posthog . get_config ());
Convex Auth Errors
Problem: Unauthenticated errors in Convex queries
Solution: Verify CLERK_JWT_ISSUER_DOMAIN matches Clerk dashboard
# Get from Clerk Dashboard > API Keys > Advanced > JWT Template
CLERK_JWT_ISSUER_DOMAIN = clerk.your-domain.com
Next Steps
Architecture Learn about the overall tech stack and data flow
Authentication Deep dive into Clerk authentication setup
Convex Guide Learn how to write queries and mutations
Theming Customize colors and styles