Zum Hauptinhalt springen
Version: latest

Blog-Karten-Raster-Muster

Ein responsives Karten-Raster ist das Standardlayout für Blog-Index-Seiten, Artikel-Listen und Content-Hubs. Dieses Muster verwendet die prokodo-UI-Komponenten PostTeaser, Grid und Pagination für eine vollständig serverseitig gerenderte Listing-Seite mit optionaler Pagination.


Wann dieses Muster verwenden

  • Blog-Index-Seiten, Artikel-Archive und Newsfeeds
  • Content-Marketing-Hubs aus einem Headless CMS
  • Listing-Seiten, bei denen visuelle Hierarchie und LCP-Performance entscheidend sind

Client-Pagination

app/blog/BlogPagination.tsx
"use client"

import { useRouter } from "next/navigation"
import { Pagination } from "@prokodo/ui/pagination/client"
import "@prokodo/ui/pagination.css"

export function BlogPagination({
page,
totalPages,
}: {
page: number
totalPages: number
}) {
const router = useRouter()
return (
<Pagination
page={page}
totalPages={totalPages}
onPageChange={p => router.push(`?page=${p}`)}
style={{ marginTop: "3rem" }}
/>
)
}

Statisches Listing (RSC, empfohlen)

app/blog/page.tsx
import { Grid } from "@prokodo/ui/grid"
import { PostTeaser } from "@prokodo/ui/post-teaser"
import { BlogPagination } from "./BlogPagination"
import "@prokodo/ui/grid.css"
import "@prokodo/ui/post-teaser.css"

interface SearchParams {
page?: string
}

export default async function BlogPage({
searchParams,
}: {
searchParams: Promise<SearchParams>
}) {
const sp = await searchParams
const page = Number(sp.page ?? 1)
const limit = 12

const { posts, total } = await fetchPosts({ page, limit })

return (
<main style={{ padding: "2rem", maxWidth: "1280px", margin: "0 auto" }}>
<h1>Blog</h1>

<Grid columns={{ xs: 1, sm: 2, lg: 3 }} gap="lg">
{posts.map(post => (
<PostTeaser
key={post.slug}
title={{ content: post.title }}
content={post.excerpt}
image={{ src: post.coverImage, alt: post.title }}
redirect={{ href: `/blog/${post.slug}`, label: "Weiterlesen" }}
date={post.publishedAt}
locale="de"
category={post.category}
/>
))}
</Grid>

<BlogPagination page={page} totalPages={Math.ceil(total / limit)} />
</main>
)
}

CMS-Integration (Strapi-Beispiel)

lib/posts.ts
export async function fetchPosts({
page = 1,
limit = 12,
}: {
page?: number
limit?: number
}) {
const qs = new URLSearchParams({
"pagination[page]": String(page),
"pagination[pageSize]": String(limit),
populate: "coverImage,author,category",
sort: "publishedAt:desc",
})

const res = await fetch(
`${process.env.STRAPI_URL}/api/articles?${qs}`,
{ next: { revalidate: 60 } }, // ISR
)
if (!res.ok) throw new Error("Artikel konnten nicht geladen werden")
const json = await res.json()
return { posts: json.data.map(mapPost), total: json.meta.pagination.total }
}

SEO & Metadaten

app/blog/page.tsx (Metadaten-Export)
export const metadata = {
title: "Blog — prokodo UI",
description: "Aktuelle Artikel zu React, Next.js und Component-Design.",
alternates: {
canonical: "https://example.com/de/blog",
},
}

Performance-Tipps

TechnikUmsetzung
LCP-Bild priorisieren<PostTeaser imagePriority /> bei den ersten 3 Karten
Responsive Imagessizes-Prop am PostTeaser-Bild setzen
ISRfetch(…, { next: { revalidate: 60 } })
Prefetch beim Hover<Link prefetch> auf Karten-Wrapper verwenden

Verwandte Komponenten

  • PostTeaser — Karte mit Bild, Titel, Auszug, Metadaten
  • Grid — responsiver CSS-Grid-Container
  • Pagination — URL-gesteuerte Seitennavigation
  • Card — generischer Karten-Container für Nicht-Artikel-Inhalte

Weiterführende Informationen


👉 Blog-Karten-Raster in Storybook öffnen