Documentation is currently in beta. Report issues →
ArchitectureData Flow

Data Flow

Understanding how data flows through Y3NKO is key to working with the codebase.

Request Lifecycle

Browser Request


┌─────────────────┐
│    Middleware   │  ← Auth check, redirects
└─────────────────┘


┌─────────────────┐
│   Layout.tsx    │  ← Wraps all pages
└─────────────────┘


┌─────────────────┐
│   Page.tsx      │  ← Server Component (default)
│  (async data)   │
└─────────────────┘


┌─────────────────┐
│  Client Comps   │  ← Interactive elements
└─────────────────┘

Server Components vs Client Components

Server Components (Default)

Used for:

  • Fetching data
  • Accessing backend resources
  • Keeping sensitive info on server
  • Large dependencies
// app/accommodations/page.tsx (Server Component)
import { getListings } from '@/lib/actions/listings'
 
export default async function AccommodationsPage() {
  const listings = await getListings()
 
  return (
    <div>
      {listings.map(listing => (
        <ListingCard key={listing.id} listing={listing} />
      ))}
    </div>
  )
}

Client Components

Used for:

  • Interactivity (onClick, onChange)
  • Browser APIs (localStorage, window)
  • React hooks (useState, useEffect)
  • Real-time updates
// components/features/search/search-bar.tsx
"use client"
 
import { useState } from 'react'
 
export function SearchBar({ onSearch }: SearchBarProps) {
  const [query, setQuery] = useState('')
 
  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      onKeyDown={(e) => e.key === 'Enter' && onSearch(query)}
    />
  )
}

Data Fetching Patterns

1. Server Component Fetch

Direct database access in Server Components:

// app/accommodations/[slug]/page.tsx
import { prisma } from '@/lib/db'
 
export default async function ListingPage({ params }) {
  const listing = await prisma.listing.findUnique({
    where: { slug: params.slug },
    include: { images: true, amenities: true }
  })
 
  return <ListingDetail listing={listing} />
}

2. Server Actions

For mutations (create, update, delete):

// lib/actions/bookings.ts
"use server"
 
export async function createBooking(data: BookingInput) {
  const session = await getServerSession()
  if (!session) throw new Error('Unauthorized')
 
  return await prisma.booking.create({
    data: {
      ...data,
      userId: session.user.id
    }
  })
}

Using in a Client Component:

"use client"
 
import { createBooking } from '@/lib/actions/bookings'
 
function BookingForm() {
  async function handleSubmit(formData: FormData) {
    const result = await createBooking({
      listingId: formData.get('listingId'),
      checkIn: formData.get('checkIn'),
      // ...
    })
  }
 
  return <form action={handleSubmit}>...</form>
}

3. API Routes

For webhooks and external integrations:

// app/api/webhooks/paystack/route.ts
export async function POST(request: Request) {
  const body = await request.json()
  const signature = request.headers.get('x-paystack-signature')
 
  // Verify webhook, process payment
 
  return Response.json({ received: true })
}

State Management

Y3NKO uses a minimal state approach:

State TypeSolution
Server stateServer Components, Server Actions
URL stateSearch params, route params
Form stateReact Hook Form
UI stateuseState, useReducer
Global UIReact Context (minimal)

URL as State

Filters and search are stored in URL params:

// /accommodations?region=Accra&minPrice=100&maxPrice=500
"use client"
 
import { useSearchParams, useRouter } from 'next/navigation'
 
function Filters() {
  const searchParams = useSearchParams()
  const router = useRouter()
 
  function updateFilter(key: string, value: string) {
    const params = new URLSearchParams(searchParams)
    params.set(key, value)
    router.push(`/accommodations?${params.toString()}`)
  }
}

Caching Strategy

Next.js 14 caching layers:

CacheScopeDurationRevalidation
Request MemoizationSingle requestRequest lifetimeAutomatic
Data CacheAll requestsIndefiniterevalidatePath(), revalidateTag()
Full Route CacheStatic routesBuild timeRebuild or on-demand

Revalidation Example

// After creating a booking
"use server"
 
import { revalidatePath } from 'next/cache'
 
export async function createBooking(data) {
  const booking = await prisma.booking.create({ data })
 
  // Revalidate affected pages
  revalidatePath('/dashboard/bookings')
  revalidatePath(`/accommodations/${data.listingSlug}`)
 
  return booking
}

Error Handling

Errors flow through the component tree:

Page.tsx

  ├── try/catch in Server Component
  │     └── Show error UI inline

  └── error.tsx boundary
        └── Catches unhandled errors
// app/accommodations/error.tsx
"use client"
 
export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Something went wrong</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  )
}