--- import { getEmDashCollection, getTermsForEntries, getSiteSettings, } from "emdash"; import { Image } from "emdash/ui"; import Base from "../layouts/Base.astro"; import PostCard from "../components/PostCard.astro"; import { getReadingTime } from "../utils/reading-time"; import { resolveBlogSiteIdentity } from "../utils/site-identity"; // Limit to what we render (1 featured + 6 grid). The DB does the slicing // instead of fetching every post and discarding the tail in JS. const POSTS_PER_PAGE = 7; const [{ entries: posts, cacheHint }, settings] = await Promise.all([ getEmDashCollection("posts", { orderBy: { published_at: "desc" }, limit: POSTS_PER_PAGE + 1, // +1 to detect "view all" need }), getSiteSettings(), ]); const { siteTitle, siteTagline } = resolveBlogSiteIdentity(settings); Astro.cache.set(cacheHint); // Trim the lookahead post used to detect overflow const visiblePosts = posts.slice(0, POSTS_PER_PAGE); const hasMorePosts = posts.length > POSTS_PER_PAGE; // Find the first post with a featured image for the hero const featuredPost = visiblePosts.find((p) => p.data.featured_image); const featuredIndex = featuredPost ? visiblePosts.indexOf(featuredPost) : -1; // Get remaining posts (exclude featured if found, limit to 6 for grid) const gridPosts = visiblePosts.filter((_, i) => i !== featuredIndex).slice(0, 6); // Single batched query for tags across the featured post + grid posts. // Avoids the N+1 pattern of calling getEntryTerms() per entry. // Bylines are already hydrated on entry.data.bylines by getEmDashCollection. const tagEntryIds = [ ...(featuredPost ? [featuredPost.data.id] : []), ...gridPosts.map((p) => p.data.id), ]; const tagsByEntry = await getTermsForEntries("posts", tagEntryIds, "tag"); const featuredTags = featuredPost ? (tagsByEntry.get(featuredPost.data.id) ?? []).map((t) => ({ slug: t.slug, label: t.label, })) : []; const featuredBylines = featuredPost?.data.bylines ?? []; const gridPostsWithTags = gridPosts.map((post) => ({ post, tags: (tagsByEntry.get(post.data.id) ?? []).map((t) => ({ slug: t.slug, label: t.label, })), bylines: post.data.bylines ?? [], })); // Format date helper function formatDate(date: Date | null | undefined) { if (!date) return null; return date.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", }); } --- { posts.length === 0 ? (

No posts yet

Create your first post in the admin panel.

Create a post
) : (
{/* Featured Post - Side by side */} {featuredPost && ( )} {/* Latest Posts */} {gridPostsWithTags.length > 0 && (

Latest

{hasMorePosts && ( View all )}
{gridPostsWithTags.map(({ post, tags, bylines }) => ( ))}
)}
) }