포스트 내용을 간략하게 보여주는 포스트 카드를 만들고 카드들을 정렬해서 보여주도록 구현
src/page.tsx
import FeaturedPosts from "@/components/FeaturedPosts";
import Hero from "@/components/Hero";
import PostsCarousel from "@/components/PostsCarousel";
export default async function HomePage() {
return (
<section>
<Hero />
{/* @ts-expect-error Server Component */}
<FeaturedPosts />
</section>
);
}
src/components에 FeaturedPosts 컴포넌트를 생성후 Home page에 Hero 컴포넌트 밑에 위치
포스트 카드를 그리드 형식으로 보여주는 컴포넌트
그리드로 보여주는 것을 재사용할 수 있도록 따로 컴포넌트로 만들었다.
src/components/FeaturedPosts.ts
import { getFeaturedPosts } from "@/service/posts";
import PostsCard from "./PostCard";
import PostsGrid from "./PostsGrid";
export default async function FeaturedPosts() {
return (
<section>
<h2>Featured Posts</h2>
<PostsGrid/>
</section>
);
}
PostsGrid 컴포넌트를 FeaturedPosts에 import하여 보여지도록 함
src/components/PostsGrid.ts
import { Post } from "@/service/posts";
import PostCard from "./PostCard";
export default function PostsGrid({ posts }: { posts: Post[] }) {
return (
<ul className="grid gap-4 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{posts.map((el) => {
return (
<li key={el.title}>
<PostCard post={el} />
</li>
);
})}
</ul>
);
}
css grid를 사용하여 post card들을 배치하는 틀을 만들었다.
컴포넌트화하여 이 틀을 다른 곳에서도 재사용할 수 있도록했다.
화면 크기에따라 열의 개수가 달라지는 반응형으로 만들었다.
위의 코드에서 확인할 수 있듯이 PostsGrid 컴포넌트 안에 import 하여 사용
정리하자면
homepage > FeaturedPosts > PostsGrid > PostCard
구조로 구성되어있다.
src/components/PostCard.ts
import { Post } from "@/service/posts";
import Image from "next/image";
import Link from "next/link";
type Props = { post: Post };
export default function PostCard({
post: { title, description, date, category, path },
}: Props) {
return (
<Link href={`/posts/${path}`}>
<article className="w-full pb-5 rounded-md overflow-hidden bg-white shadow-md hover:shadow-xl">
<div className="w-full h-40 ">
<Image
src={`/image/posts/${path}.png`}
alt={title}
width={300}
height={200}
className="w-full h-full object-cover"
/>
</div>
<div className=" flex flex-col justify-center items-center">
<time className=" self-end mb-2 mr-2 text-slate-400">
{date.toString()}
</time>
<h1 className=" font-bold text-xl">{title}</h1>
<p className="mb-3 px-5 w-full truncate text-center">{description}</p>
<span className="bg-sky-200 px-3 py-1 rounded-md text-sm">
{category}
</span>
</div>
</article>
</Link>
);
}
비즈니스 로직은 다량의 데이터를 읽어오는 것 같은 복잡한 로직을 의미한다.
이런 복잡한 로직은 컴포넌트 내에 담고 있는 것이 아니라
복잡한 로직을 담당하는 모듈에게 전가해야한다.
따라서 프로젝트의 source 폴더 안에 service
폴더를 생성하고 (api, manager 등이라고 해도 좋다) 그 안에 비즈니스 로직들만 담당하는 파일들을 생성해 작업하는 것이 바람직하다.
따라서 featured posts에 필요한 posts 데이터들을 읽어오기 위해
src/service/posts.ts 모듈을 생성해 비즈니스 로직을 작성했다.
import path from 'path';
import { readFile } from "fs/promises";
export type Post = {
title: string;
description: string;
date: Date;
category: string;
path: string;
featured: boolean;
}
export async function getAllPosts():Promise<Post[]> {
// 비동기 함수
// Promise<Post[]> 호출하면 Post의 배열 Promise를 반환한다는 의미
const filePath = path.join(process.cwd(), "data", "posts.json");
// process.cwd()는 process가 현재 동작하고 있는 현재 경로를 받아오는 것을 의미
// public/data/posts.json을 받아오도록 설정함
return (
readFile(filePath, "utf-8")
// 받아온 파일을 읽기 위해 readFile 사용
// readFile은 promise에 있는걸로 가져와야함
// 그래야 프로미스를 반환하는 readFile을 사용할 수 있음
.then<Post[]>(JSON.parse)
// data를 가지고 와서 JSON에 있는 parse에 그대로 전달
// .then(data => JSON.parse(data))의 축약버전임
// 전달하는 것과 인자가 같을 때 생략 가능
// <Post[]>제네릭으로 타입을 지정해줘야
//아래 sort(a, d) 에서 a, d에 대한 타입 에러가 안남
.then((posts) => posts.sort((a, d) => (a.date > d.date ? -1 : 1)))
);
// 최신순으로 정렬
}
작성해주신 포스팅들이 최근에 읽은 Next js 관련 글들 가운데 가장 잘 읽혔던 것 같습니다.
저도 Next js로 블로그 제작하고 있었는데, 이번에 Next 14 버전에서 너무 많은 업데이트가 있어 공부 중 입니다.
Next 14 관련 글 써주시면 후다닥 보러 오겠습니다.ㅎㅎ
잘 정리된 포스팅들 감사합니다.