| name | wsh-home-optimization |
| description | Home page performance optimization — Post defaultScope, SSR data reduction, lazy media hydration for WSH 2026 CaX app |
WSH: Home Page Optimization
The home page (ホームを開く) scores 29.35/100 — the worst scoring page. Root causes are heavy Post defaultScope, massive SSR JSON, and expensive hydration of 10 timeline items with all media components.
Use this skill when optimizing the home page timeline, Post model queries, or SSR prefetch for list views.
Techniques
1. Optimize Post DefaultScope for List Views
The Post model's defaultScope eagerly loads user, images, movie, sound associations for every query. For the home timeline, this creates heavy JOINs and inflates SSR JSON.
defaultScope: {
include: [
{ association: "user", include: [{ association: "profileImage" }] },
{ association: "images", through: { attributes: [] } },
{ association: "movie" },
{ association: "sound" },
],
}
Key constraint: Single post detail pages still need all associations. Only list views (home timeline, user posts, search results) can use the lighter scope.
Impact: FCP, LCP, SI improvement by reducing SSR response time and JSON size.
2. Lazy Load Media Components
SoundPlayer and PausableMovie are heavy components that block hydration:
- SoundPlayer fetches peaks data via useFetch and renders SVG waveform
- PausableMovie creates IntersectionObserver and manages video playback
const SoundArea = React.lazy(() => import('./SoundArea'));
const MovieArea = React.lazy(() => import('./MovieArea'));
{post.sound && (
<Suspense fallback={<div style={{ height: 80 }} />}>
<SoundArea sound={post.sound} />
</Suspense>
)}
Impact: TBT improvement by deferring heavy component initialization.
3. Reduce SSR Data Size
The SSR prefetch serializes full Post objects with all associations as inline JSON:
window.__SSR_DATA__ contains 10 posts × (user + images + movie + sound)
- Large inline
<script> blocks FCP (HTML parsing takes longer)
Options:
- Exclude binary/large fields from serialization
- Reduce number of prefetched posts (e.g., 5 instead of 10)
- Exclude movie/sound data from list SSR (load on client)
Impact: FCP, LCP by reducing HTML size.
Pitfalls
| Pitfall | Symptom | Fix |
|---|
| Removing associations breaks detail pages | Single post pages show no images/video/audio | Only apply light scope to list endpoints, keep full scope for detail |
| Lazy loading breaks SSR hydration | Hydration mismatch errors in console | Use Suspense with appropriate fallback that matches server HTML |
| Changing defaultScope affects other queries | Various pages break | Use Post.unscoped().findAll() with explicit includes instead of modifying defaultScope |
VRT Risks
| Change | Visual Impact | Mitigation |
|---|
| Removing associations from list view | Media might not render on timeline | Ensure images still load (they're needed for timeline display) |
| Lazy loading media | Slight layout shift during load | Use fixed-height placeholders matching current dimensions |
| Reducing SSR data | Initial flash of loading states | Ensure above-fold content still SSR renders correctly |
Project-Specific Notes
- Post model at
server/src/models/Post.ts
- SSR prefetch at
server/src/ssr-prefetch.ts
- Post list endpoint at
server/src/routes/api/post.ts
- Timeline components at
client/src/components/timeline/
- SoundPlayer at
client/src/components/foundation/SoundPlayer.tsx
- PausableMovie at
client/src/components/foundation/PausableMovie.tsx
- The home page SSR prefetches 10 posts with
Post.findAll({ limit: 10, offset: 0 })
- SQLite database is ~94MB, copied to tmpdir on server start