Skip to content

Task 3: Traditional Version (10-14 hours) #6

@AbanoubGhadban

Description

@AbanoubGhadban

Overview

Build the traditional React SSR with client-side data fetching version.

Dependency: Task 2 (Shared Components & API)
Can Run Parallel With: Task 4 (RSC Version)

Key Architecture

  • "use client" at root level → all components become client components
  • Components go into the client bundle (NOT RSC bundle)
  • Static parts SSRed, but lazy-loaded components are NOT SSRed
  • Only Suspense fallbacks (spinners) render on server for lazy components
  • Data fetches client-side via useEffect/fetch

Deliverables

Rails View

<!-- app/views/restaurants/search.html.erb -->
<div class="search-page">
  <%= react_component("SearchPage", { restaurant_id: @restaurant.id }) %>
</div>

Components

Component Type Purpose
SearchPage.tsx Entry SSRed by react_component helper
SearchPageContent.tsx Client ("use client") Main content with hooks
AsyncStatus.tsx Lazy-loaded Fetches status client-side
AsyncWaitTime.tsx Lazy-loaded Fetches wait time client-side
AsyncSpecials.tsx Lazy-loaded Fetches specials client-side
AsyncTrending.tsx Lazy-loaded Fetches trending client-side
AsyncRating.tsx Lazy-loaded Fetches rating client-side

Pattern

// SearchPageContent.tsx
"use client";

import { Suspense, lazy } from 'react';
const AsyncStatus = lazy(() => import('./AsyncStatus'));

export function SearchPageContent({ restaurantId }: Props) {
  return (
    <Suspense fallback={<Spinner />}>
      <AsyncStatus restaurantId={restaurantId} />
    </Suspense>
  );
}

// AsyncStatus.tsx (lazy-loaded, NOT SSRed)
export default function AsyncStatus({ restaurantId }: Props) {
  const [status, setStatus] = useState(null);
  useEffect(() => {
    fetch(`/api/restaurants/${restaurantId}/status`)
      .then(r => r.json())
      .then(setStatus);
  }, [restaurantId]);
  return status ? <StatusBadge status={status} /> : null;
}

Performance Monitoring

  • Collect Web Vitals (LCP, CLS, INP)
  • Measure "Lazy Load Time" (mount to display)

Success Criteria

  • /search page loads and renders
  • Static parts SSR completely
  • Spinners show while loading (lazy components NOT SSRed)
  • Lazy components load in separate chunks
  • API calls visible in DevTools Network tab
  • LCP: ~500-600ms
  • CLS: ~0.10-0.15
  • Console shows no errors

Expected Timeline

0-50ms:   SSRed HTML received (spinners visible)
50-100ms: JS bundles download
100-150ms: React hydrates
150-200ms: Lazy components load, useEffect fires
200-350ms: API requests in flight
350-500ms: Responses received, spinners replaced
500-600ms: All content visible (LCP)

Key Files to Create

app/javascript/entries/search.tsx
app/javascript/components/search/SearchPage.tsx
app/javascript/components/async/traditional/
├─ SearchPageContent.tsx
├─ AsyncStatus.tsx
├─ AsyncWaitTime.tsx
├─ AsyncSpecials.tsx
├─ AsyncTrending.tsx
└─ AsyncRating.tsx
app/views/restaurants/search.html.erb

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions