perf: trim critical-path CSS and warm map/data connections - #10
Conversation
Move the vendored mapbox-gl stylesheet (~40KB raw / 5.4KB gzip) out of the render-blocking globals.css and into MapView, which is loaded via a lazy dynamic(ssr:false) import. The stylesheet now ships with the map chunk instead of every first paint, cutting render-blocking critical CSS from 108.5KB to 68.7KB raw (17.7KB -> 12.4KB gzip, -30%) with no visual change — MapView is the only consumer of those styles. Add preconnect/dns-prefetch resource hints for Mapbox (api/events) and the Supabase origin so the TLS handshakes for tiles, styles and photos start during HTML parse rather than after the map/gallery code runs. Add decoding="async" to the Journey hero slideshow images so large photo decodes stay off the main thread. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bu6yr5RHkXvk7kXYdL24tu
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughMapbox CSS and styling overrides are relocated from the global stylesheet to a dedicated ChangesPage Load Performance and Bundle Size Optimization
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
components/MapView.tsx (1)
4-6: Organize Mapbox styles into a separate file for better code maintainability.The suggestion to move
.mapboxgl-*related style overrides fromapp/globals.cssto a map-specific stylesheet (e.g.,components/mapbox-overrides.css) imported aftermapbox-gl/dist/mapbox-gl.cssis a good organizational practice. This improves code clarity by keeping map-specific styling separate from global application styles.However, the current implementation already mitigates cascade concerns: the Mapbox overrides in
app/globals.css(lines 33–69) extensively use!importantdeclarations, ensuring they take precedence over vendor styles regardless of load order.Suggested refactoring
import mapboxgl from "mapbox-gl"; // Loaded here rather than in globals.css so the vendored stylesheet ships with // this lazily-imported (ssr:false) map chunk, keeping it off the critical path. import "mapbox-gl/dist/mapbox-gl.css"; +import "./mapbox-overrides.css";Then move
.mapboxgl-*and related map control/popup styles fromapp/globals.cssintocomponents/mapbox-overrides.css.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/MapView.tsx` around lines 4 - 6, Extract the Mapbox style overrides (the `.mapboxgl-*` and related map control/popup styles currently in `app/globals.css` at lines 33–69) into a new separate stylesheet file named `components/mapbox-overrides.css`. Then import this new stylesheet in MapView.tsx immediately after the existing `mapbox-gl/dist/mapbox-gl.css` import statement to maintain the proper cascade order while keeping map-specific styles organized separately from global application styles.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@components/MapView.tsx`:
- Around line 4-6: Extract the Mapbox style overrides (the `.mapboxgl-*` and
related map control/popup styles currently in `app/globals.css` at lines 33–69)
into a new separate stylesheet file named `components/mapbox-overrides.css`.
Then import this new stylesheet in MapView.tsx immediately after the existing
`mapbox-gl/dist/mapbox-gl.css` import statement to maintain the proper cascade
order while keeping map-specific styles organized separately from global
application styles.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 630c157a-8bb6-4446-9438-66eeccc789b8
📒 Files selected for processing (4)
app/globals.cssapp/layout.tsxcomponents/JourneyHeroCard.tsxcomponents/MapView.tsx
Follow-up to the mapbox-gl.css relocation: extract the Mapbox control/popup and custom photo-marker overrides out of globals.css into components/map-overrides.css, imported by MapView after the vendor sheet. Every selector moved targets DOM that only exists once the map mounts (verified: mapboxgl/lofoten-photo-marker/ lofoten-popup/lofoten-outlier/map-unavailable classes are referenced only by the map components, all children of the lazy MapView). The hero Ken Burns animation and .font-serif stay in globals since they render independently of the map. This trims render-blocking critical CSS further: 68.7KB -> 63.1KB raw (12.4KB -> 11.1KB gzip). Combined with the earlier change, critical CSS is down from 108.5KB to 63.1KB raw (-42%) and 17.7KB to 11.1KB gzip (-37%); the moved styles now ship in the lazy map chunk. Addresses CodeRabbit's review nitpick on PR #10. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bu6yr5RHkXvk7kXYdL24tu
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@components/map-overrides.css`:
- Around line 111-132: The `.lofoten-photo-highlight-ring` class applies an
infinite animation without respecting user accessibility preferences. Add a
`@media (prefers-reduced-motion: reduce)` media query block after the keyframes
definition that targets the `.lofoten-photo-highlight-ring` class and disables
or removes the animation property to honor users' reduced motion preferences.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 2354115d-246b-4f2c-aeaf-13bc2556694c
📒 Files selected for processing (3)
app/globals.csscomponents/MapView.tsxcomponents/map-overrides.css
🚧 Files skipped from review as they are similar to previous changes (1)
- components/MapView.tsx
The pulsing ring that marks the photo open in the editor ran an infinite animation with no reduced-motion fallback. Add a prefers-reduced-motion guard so the ring stays visible but holds still for those users — matching the existing treatment of the hero Ken Burns animation. Addresses CodeRabbit's review comment on PR #10. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bu6yr5RHkXvk7kXYdL24tu
Inspired by an alternate PR that swapped @turf/length for an inline haversine in geo.ts. On its own that saved nothing — Turf still reached the initial page graph through two other paths — so this carries the idea through to actually remove it: - lib/geo.ts: inline haversine (segmentDistanceMeters/geometryDistanceMeters) replaces @turf/length for bounds and route-distance math, plus a distanceKm helper. Uses Turf's mean Earth radius so displayed distances are unchanged. - lib/photo-outliers.ts: pull distanceKm from geo instead of journey-leg. It only needed that one function, but importing it dragged journey-leg's eight @turf/* packages into the initial bundle (via AdminDataPanel in the always-mounted sidebar). JourneyMiniMap still uses journey-leg in its own lazy chunk. - lib/hooks/useTripMutations.ts: load @/lib/gpx (and its @turf/simplify dep) lazily inside the importGpx callback — a rare, already-async admin action. Net: Turf is no longer present in the initial JS at all (verified by scanning the chunks the prerendered document loads). Initial JS drops from ~1032KB to 1014KB raw / ~302KB to 295.6KB gzip. Turf stays installed for the dynamic journey/upload chunks that genuinely need it; behavior is unchanged (192 tests pass). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bu6yr5RHkXvk7kXYdL24tu
Summary
Performance pass focused on the critical rendering path — the bytes and round-trips between request and first interactive paint — without changing any behavior or visual output. I audited the bundle, measured a baseline production build, applied the changes, and re-measured.
The codebase was already well-optimized (React Compiler on;
mapbox-gl,exifreader, and all heavy panels behinddynamic(); Turf imported by subpath; system fonts only), so the wins target what still ships eagerly to every visitor.Changes
mapbox-glstylesheet off the critical path. It was@imported inglobals.css— render-blocking on every load — even though the map JS is lazy (dynamic(ssr:false)). Moved the import intoMapView.tsx(the only consumer), so it ships in the lazy map chunk instead.api/events) and the Supabase origin, so TLS handshakes for tiles, styles, and photos start during HTML parse rather than after the map/gallery code executes.decoding="async"on the Journey hero slideshow images to keep large photo decodes off the main thread.Quantitative results
Measured from
next build(Next 16 / Turbopack) artifacts, before vs. after.Render-blocking critical CSS (downloaded before first paint)
The 39.8 KB / 5.4 KB-gzip mapbox stylesheet now loads as a separate chunk alongside the map JS. Verified the prerendered
index.html<head>references only the app CSS chunk (mapbox CSS occurrences in head:0).Network round-trips
5 resource hints now emitted in the initial document, warming the connections to
api.mapbox.com,events.mapbox.com, and the Supabase origin before the deferred map/data code runs — saves ~1 RTT (DNS+TCP+TLS) per origin on first paint.For reference — initial JS (unchanged, already optimal)
~302 KB gzip, dominated by
@supabase/supabase-js(~90 KB gz, the data client) andreact-dom(~71 KB gz) — both genuinely on the critical path. The 1.7 MB / 467 KB-gzipmapbox-glengine stays correctly code-split into its own lazy chunk.Verification
npm run build✓npm run lint✓ (1 pre-existing, unrelated warning)npm run typecheck✓npm test✓ — 192/192 passing🤖 Generated with Claude Code
Generated by Claude Code
Summary by CodeRabbit
preconnectanddns-prefetchhints for the detected origin.