Learning
Next.js

Gyakorlati példák

// app/blog/page.tsx – Blog lista oldal

15. Gyakorlati példák

Blog alkalmazás

// app/blog/page.tsx – Blog lista oldal
import Link from 'next/link'

async function getPosts() {
  'use cache'
  cacheLife('hours')
  cacheTag('posts')
  return await db.posts.findAll({ published: true })
}

export default async function BlogPage() {
  const posts = await getPosts()

  return (
    <main className="max-w-3xl mx-auto py-12 px-4">
      <h1 className="text-4xl font-bold mb-8">Blog</h1>
      <div className="space-y-6">
        {posts.map(post => (
          <article key={post.id} className="border-b pb-6">
            <Link href={`/blog/${post.slug}`}>
              <h2 className="text-2xl font-semibold hover:text-blue-600">
                {post.title}
              </h2>
            </Link>
            <p className="text-gray-600 mt-2">{post.excerpt}</p>
            <time className="text-sm text-gray-400">{post.publishedAt}</time>
          </article>
        ))}
      </div>
    </main>
  )
}
// app/blog/[slug]/page.tsx – Blog post oldal
import { notFound } from 'next/navigation'

export async function generateStaticParams() {
  const posts = await db.posts.findAll()
  return posts.map(p => ({ slug: p.slug }))
}

export async function generateMetadata({ params }) {
  const { slug } = await params
  const post = await db.posts.findBySlug(slug)
  if (!post) return {}
  return {
    title: post.title,
    description: post.excerpt,
  }
}

export default async function BlogPost({ params }) {
  const { slug } = await params
  const post = await db.posts.findBySlug(slug)

  if (!post) notFound()

  return (
    <article className="max-w-3xl mx-auto py-12 px-4 prose">
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.htmlContent }} />
    </article>
  )
}

Autentikáció

// app/actions/auth.ts
'use server'

import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'

export async function login(formData: FormData) {
  const email = formData.get('email') as string
  const password = formData.get('password') as string

  const user = await verifyCredentials(email, password)

  if (!user) {
    return { error: 'Hibás email vagy jelszó' }
  }

  const token = await createSession(user.id)
  const cookieStore = await cookies()
  cookieStore.set('session', token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    maxAge: 60 * 60 * 24 * 7, // 1 hét
  })

  redirect('/dashboard')
}

export async function logout() {
  const cookieStore = await cookies()
  cookieStore.delete('session')
  redirect('/login')
}
// app/login/page.tsx
import { login } from '@/app/actions/auth'

export default function LoginPage() {
  return (
    <div className="min-h-screen flex items-center justify-center">
      <form action={login} className="w-full max-w-sm space-y-4">
        <h1 className="text-2xl font-bold">Bejelentkezés</h1>
        <input
          name="email"
          type="email"
          placeholder="Email cím"
          className="w-full border rounded px-3 py-2"
          required
        />
        <input
          name="password"
          type="password"
          placeholder="Jelszó"
          className="w-full border rounded px-3 py-2"
          required
        />
        <button
          type="submit"
          className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700"
        >
          Bejelentkezés
        </button>
      </form>
    </div>
  )
}

Dashboard loading state-tel

// app/dashboard/page.tsx
import { Suspense } from 'react'

async function RecentOrders() {
  const orders = await fetchRecentOrders() // lassú lekérés
  return (
    <ul>
      {orders.map(o => <li key={o.id}>{o.title}</li>)}
    </ul>
  )
}

async function Stats() {
  const stats = await fetchStats() // lassú lekérés
  return (
    <div className="grid grid-cols-3 gap-4">
      <StatCard title="Bevétel" value={stats.revenue} />
      <StatCard title="Rendelések" value={stats.orders} />
      <StatCard title="Ügyfelek" value={stats.customers} />
    </div>
  )
}

export default function DashboardPage() {
  return (
    <div className="p-6 space-y-8">
      <h1 className="text-3xl font-bold">Dashboard</h1>

      <Suspense fallback={
        <div className="grid grid-cols-3 gap-4">
          {[1,2,3].map(i => <SkeletonCard key={i} />)}
        </div>
      }>
        <Stats />
      </Suspense>

      <section>
        <h2 className="text-xl font-semibold mb-4">Legutóbbi rendelések</h2>
        <Suspense fallback={<OrdersSkeleton />}>
          <RecentOrders />
        </Suspense>
      </section>
    </div>
  )
}

API fetch Client Component-ből

'use client'

// components/SearchResults.tsx
import { useState, useEffect } from 'react'

export function SearchResults({ query }: { query: string }) {
  const [results, setResults] = useState([])
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    if (!query) return
    setLoading(true)

    fetch(`/api/search?q=${encodeURIComponent(query)}`)
      .then(res => res.json())
      .then(data => {
        setResults(data.results)
        setLoading(false)
      })
  }, [query])

  if (loading) return <p>Keresés...</p>

  return (
    <ul>
      {results.map(r => (
        <li key={r.id}>{r.title}</li>
      ))}
    </ul>
  )
}

📝 Összefoglaló – 15. fejezet

  • Blog: generateStaticParams + "use cache" + revalidateTag = gyors, friss, SEO-barát
  • Auth: Server Actions + cookies() + redirect() – biztonságos, szerver oldali folyamat
  • Dashboard: Streaming + Suspense = azonnali interaktivitás, nincs loading spinner az egész oldalra
  • Client oldali fetch: useEffect + fetch API – akkor használd, ha valóban kliensnek kell

On this page