Skip to content

fix: hydration mismatch, duplicate SVG fetches, PostHog migration#88

Merged
thegdsks merged 5 commits intomainfrom
fix/cloudflare-migration
Apr 4, 2026
Merged

fix: hydration mismatch, duplicate SVG fetches, PostHog migration#88
thegdsks merged 5 commits intomainfrom
fix/cloudflare-migration

Conversation

@thegdsks
Copy link
Copy Markdown
Member

@thegdsks thegdsks commented Apr 4, 2026

Summary

  • Hydration fix: Keyboard shortcut (Cmd+K / Ctrl+K) now uses useState + useEffect instead of render-time navigator check, eliminating SSR/client mismatch
  • Duplicate fetch elimination: Download and PNG export now reuse the already-fetched svgContent instead of re-fetching the same SVG (was 3x fetches per variant switch)
  • PostHog migration: Init moved to instrumentation-client.ts (Next.js 15.3+), removed dead PostHogProvider component from layout
  • PostHog tracking: Added event tracking to PNG export, quick commands, jsDelivr copy, and blog share buttons

Test plan

  • Verify no hydration warnings in browser console
  • Switch icon variants on detail page -- network tab should show only 1 SVG fetch per switch (not 3)
  • Export PNG -- should work without extra network request
  • Download SVG -- should work without extra network request
  • Verify PostHog events fire in dev console (debug mode)
  • Check keyboard shortcut shows Cmd+K on Mac, Ctrl+K on other platforms

thegdsks and others added 2 commits April 4, 2026 00:08
- 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>
Copilot AI review requested due to automatic review settings April 4, 2026 16:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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+K hint by moving platform detection to useEffect.
  • 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.

Comment on lines 105 to +124
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());
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 33 to 35
try {
const blob = await svgToPng(currentPath, size);
const blob = await svgToPng(svgContent || currentPath, size);
const variantSuffix =
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment thread src/lib/svg-to-png.ts
Comment on lines 6 to 10
/**
* 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
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread instrumentation-client.ts
Comment on lines +7 to +9
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
ui_host: "https://us.posthog.com",
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 4, 2026

Greptile Summary

This PR addresses three concerns: SSR hydration mismatches (fixed via useState(false) + useEffect for isMac), duplicate SVG network requests (centralised fetch in icon-detail-page.tsx with the result passed down as svgContent), and PostHog migration to the Next.js 15.3+ instrumentation-client.ts entrypoint. The approach for all three is sound, but the PNG export has a correctness issue:

  • PngExport.handleExport's useCallback omits svgContent from its dependency array (line 52 of png-export.tsx). When the user switches variants, activeVariant causes the callback to be recreated, but svgContent may still hold the previous variant's markup at that moment — so a quick export after a variant switch can silently produce a PNG of the wrong icon.

Confidence Score: 4/5

Safe 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

Filename Overview
src/components/icons/detail/png-export.tsx Accepts svgContent prop to avoid re-fetch; useCallback dep array omits svgContent, causing stale-closure PNG export after a variant switch (P1).
src/components/icons/icon-detail-page.tsx Centralises SVG fetch with cancellation flag; passes svgContent down to CodeBlock and PngExport; download correctly uses cached content with fetch fallback.
instrumentation-client.ts PostHog initialisation moved to Next.js 15.3+ instrumentation-client entry point; opt-out path and cookie-based ignore logic look correct.
src/components/header.tsx isMac state initialised as false and set in useEffect, correctly eliminating the SSR/client navigator hydration mismatch.
src/components/search/search-bar.tsx Same isMac hydration fix as header; posthog search tracking added with a debounce timer.
src/lib/svg-to-png.ts Now accepts raw SVG markup or a URL; viewBox-aware aspect ratio calculation and proper blob URL cleanup look solid.
src/components/icons/detail/quick-commands.tsx PostHog tracking added; navigator.clipboard.writeText() is not awaited, risking silent failure with false copied feedback (P2).
src/components/icons/detail/jsdelivr-button.tsx PostHog tracking added; navigator.clipboard.writeText() not awaited, same false-success risk as quick-commands (P2).
src/components/blog/share-buttons.tsx PostHog tracking added to share and copy-link actions; handleCopyLink correctly awaits the clipboard write.
src/components/icons/icon-card.tsx PostHog events added to copy, download, and icon_copied actions on the grid card; no logic changes.
src/components/icons/icon-detail.tsx Quick-preview modal; handleDownload still re-fetches SVG (minor inconsistency, no regression).

Sequence Diagram

sequenceDiagram
    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
Loading

Comments Outside Diff (1)

  1. src/components/icons/detail/png-export.tsx, line 52 (link)

    P1 Stale svgContent closure can export the wrong variant

    svgContent is consumed on line 34 (svgToPng(svgContent || currentPath, size)) but is absent from the useCallback dependency array. When the user switches variants, activeVariant changes and the callback is recreated — but at that instant svgContent may still hold the previous variant's markup (the async fetch in icon-detail-page.tsx hasn't resolved yet). The fallback || currentPath is skipped because svgContent is a non-empty string, so the wrong SVG gets converted to PNG.

    Add svgContent to the dependency array:

Reviews (1): Last reviewed commit: "fix: remove PostHogProvider references r..." | Re-trigger Greptile

Comment thread src/components/icons/detail/quick-commands.tsx
Comment thread src/components/icons/detail/jsdelivr-button.tsx
- 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
@thegdsks thegdsks force-pushed the fix/cloudflare-migration branch from 4e48401 to 7ac7fb4 Compare April 4, 2026 16:32
@thegdsks thegdsks merged commit 0a306bf into main Apr 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants