fix: hydration mismatch, duplicate SVG fetches, PostHog migration#88
fix: hydration mismatch, duplicate SVG fetches, PostHog migration#88
Conversation
- Enable Next.js static export (output: export) - Remove free API routes (paid API coming to api.thesvg.org) - Remove API docs page and components - Remove dynamic OG image generation, use static fallback - Convert RSS feed to build-time generation - Add Cloudflare Pages cache headers - Update all navigation links and references - Set dynamicParams=false for static export compatibility - Fix PostHog init for static export (client-side provider) - Disable icon link prefetch to prevent RSC 404s - Fix variant switch flicker on icon detail page - Remove Vercel-specific configuration Co-Authored-By: Glinr <bot@glincker.com>
…rate PostHog to instrumentation - Fix kbd shortcut hydration error by deferring isMac detection to useEffect - Eliminate redundant SVG fetches in download and PNG export by reusing cached svgContent - Move PostHog init to instrumentation-client.ts (Next.js 15.3+), remove dead provider component - Add PostHog tracking to detail page actions (PNG export, quick commands, jsdelivr, share) Co-Authored-By: Glinr <bot@glincker.com>
There was a problem hiding this comment.
Pull request overview
This PR targets client-side UX correctness and analytics while reducing redundant network activity on icon detail interactions in the Next.js app.
Changes:
- Fixes SSR hydration mismatch for the
Cmd/Ctrl+Khint by moving platform detection touseEffect. - Reuses already-fetched SVG markup for SVG downloads and PNG exports to avoid redundant SVG fetches.
- Migrates and expands PostHog initialization/tracking (instrumentation-client) and adds new event captures across UI actions.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/svg-to-png.ts | Updates PNG conversion helper to accept either raw SVG markup or a URL. |
| src/components/search/search-bar.tsx | Moves Mac detection to state/effect to prevent hydration mismatch. |
| src/components/icons/icon-detail.tsx | Disables Next.js Link prefetch for icon detail CTA link. |
| src/components/icons/icon-detail-page.tsx | Fetches and stores SVG text for reuse; wires it into download + PNG export; adds cancellation guard. |
| src/components/icons/icon-card.tsx | Disables prefetch on icon card links. |
| src/components/icons/detail/quick-commands.tsx | Adds PostHog capture when a quick command is copied. |
| src/components/icons/detail/png-export.tsx | Accepts svgContent and uses it for SVG→PNG conversion; adds PostHog capture. |
| src/components/icons/detail/jsdelivr-button.tsx | Adds PostHog capture when jsDelivr URL is copied. |
| src/components/header.tsx | Moves Mac detection to state/effect to prevent hydration mismatch. |
| src/components/blog/share-buttons.tsx | Adds PostHog capture for link copy and share button clicks. |
| public/feed.xml | Updates RSS lastBuildDate. |
| instrumentation-client.ts | Changes PostHog api_host to come from an env var. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| useEffect(() => { | ||
| if (!currentPath) return; | ||
| let cancelled = false; | ||
| fetch(currentPath) | ||
| .then((r) => r.text()) | ||
| .then((text) => { | ||
| if (!cancelled) setSvgContent(text); | ||
| }) | ||
| .catch(() => { | ||
| if (!cancelled) setSvgContent(""); | ||
| }); | ||
| return () => { cancelled = true; }; | ||
| }, [currentPath]); | ||
|
|
||
| const handleDownload = useCallback(async () => { | ||
| if (!currentPath || downloaded) return; | ||
| try { | ||
| const res = await fetch(currentPath); | ||
| const blob = await res.blob(); | ||
| const blob = svgContent | ||
| ? new Blob([svgContent], { type: "image/svg+xml" }) | ||
| : await fetch(currentPath).then((r) => r.blob()); |
There was a problem hiding this comment.
svgContent is not cleared when currentPath changes, so during a variant switch it can temporarily contain the previous variant’s SVG. Because handleDownload prefers any truthy svgContent, a user can download the wrong SVG if they click before the new fetch completes. Consider resetting svgContent when currentPath updates and/or tracking the path that svgContent corresponds to and only using it when it matches currentPath.
| try { | ||
| const blob = await svgToPng(currentPath, size); | ||
| const blob = await svgToPng(svgContent || currentPath, size); | ||
| const variantSuffix = |
There was a problem hiding this comment.
handleExport reads svgContent (via svgToPng(svgContent || currentPath, …)), but svgContent isn’t included in the useCallback dependencies. If svgContent updates after the fetch resolves (with the same currentPath), the callback can keep using the stale empty value and re-fetch from currentPath, reintroducing duplicate requests. Include svgContent in the dependency list (or avoid capturing it in the callback).
| /** | ||
| * Converts an SVG at the given URL to a PNG Blob at the requested pixel size. | ||
| * | ||
| * Handles SVGs with or without explicit width/height by relying on viewBox. | ||
| * If the SVG has no viewBox or dimensions, it falls back to rendering at the |
There was a problem hiding this comment.
The doc comment above svgToPng still says it “Converts an SVG at the given URL…”, but the function now accepts either raw SVG markup or a URL. Update the comment to reflect the new svgSource behavior so callers understand they can pass SVG text directly.
| posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, { | ||
| api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, | ||
| ui_host: "https://us.posthog.com", |
There was a problem hiding this comment.
api_host is now read from process.env.NEXT_PUBLIC_POSTHOG_HOST, but there are no other references to this env var in the repo. If it’s unset, PostHog may default to an unexpected host or fail to send events. Consider providing a safe fallback (e.g. the previous https://us.i.posthog.com value or /ingest) and/or documenting the required env var in a checked-in env/example file.
Greptile SummaryThis PR addresses three concerns: SSR hydration mismatches (fixed via
Confidence Score: 4/5Safe to merge after fixing the missing svgContent dependency in PngExport's useCallback. One P1 finding: the stale-closure bug in PngExport can export the wrong variant's SVG as a PNG when the user switches variants quickly. The two P2 clipboard issues are UX quality concerns and do not block merge. All other changes — hydration fix, fetch deduplication, PostHog migration — are correct. src/components/icons/detail/png-export.tsx (missing svgContent dep); src/components/icons/detail/quick-commands.tsx and jsdelivr-button.tsx (unawaited clipboard promises) Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant IconDetailPage
participant PngExport
participant svgToPng
User->>IconDetailPage: Switch variant
IconDetailPage->>IconDetailPage: setActiveVariant(newVariant)
IconDetailPage->>IconDetailPage: fetch(currentPath) [async]
Note over IconDetailPage: cancelled flag guards stale state
alt svgContent arrives before export click
IconDetailPage-->>PngExport: svgContent = newVariantSVG
User->>PngExport: Click PNG export
PngExport->>svgToPng: svgToPng(newVariantSVG, size)
svgToPng-->>User: Correct PNG
else user clicks export before fetch resolves
User->>PngExport: Click PNG export
Note over PngExport: stale svgContent = oldVariantSVG
PngExport->>svgToPng: svgToPng(oldVariantSVG, size)
svgToPng-->>User: Wrong variant PNG
end
|
- Fix duplicate function signatures in jsdelivr-button and quick-commands from merge - Add missing svgContent dependency in png-export useCallback - Use useSyncExternalStore for isMac detection to avoid setState-in-effect warning
4e48401 to
7ac7fb4
Compare
Summary
Cmd+K/Ctrl+K) now usesuseState+useEffectinstead of render-timenavigatorcheck, eliminating SSR/client mismatchsvgContentinstead of re-fetching the same SVG (was 3x fetches per variant switch)instrumentation-client.ts(Next.js 15.3+), removed deadPostHogProvidercomponent from layoutTest plan
Cmd+Kon Mac,Ctrl+Kon other platforms