포스트 페이지의 context path
는 /r/{SubName}/{identifier}/{slug}
의 형태를 띌 것이므로
reddit-clone-app\client\src\pages\r\[sub]\[identifier]\[slug].tsx
파일을 생성하고 아래 내용을 입력한다.
import axios from "axios";
import { useRouter } from "next/router"
import useSWR from 'swr'
import { Post } from "../../../../types";
const PostPage = () => {
const router = useRouter();
const { identifier, sub, slug } = router.query;
// [중복 사용하는 fetcher를 생략함]
// const fetcher = async (url : string) => {
// try {
// const res = await axios.get(url);
// return res.data;
// } catch (error: any) {
// throw error.reponse.data
// }
// }
// const { data: post, error } = useSWR<Post>(identifier && slug ? `/post/${identifier}/${slug}` : null, fetcher)
const { data: post, error } = useSWR<Post>(identifier && slug ? `/post/${identifier}/${slug}` : null)
return (
<div>
PostPage
</div>
)
}
export default PostPage
SWR을 사용하는 모든 페이지에서 fetcher를 선언하지 않기 위해 최상위 컴포넌트인
reddit-clone-app\client\src\pages\_app.tsx
파일을 다음과 같은 내용으로 수정한다.
아래와 같이 수정 후 모든 페이지, 컴포넌트에서 중복 선언한 fetcher를 제거해도 무방하고
useSWR()
구문에서도 fetcher
를 참조하지 않아도 된다.
... 생략
function MyApp({ Component, pageProps }: AppProps) {
... 생략
// 재사용할 fetcher를 선언
const fetcher = async (url : string) => {
try {
const res = await axios.get(url);
return res.data;
} catch (error: any) {
throw error.reponse.data
}
}
// SWRConfig 컴포넌트 value로 앞서 선언한 fetcher를 넣어준다
// 이후 모든 컴포넌트를 SWRConfig 하위로 이동한다.
return <SWRConfig
value=
>
<AuthProvider>
{!authRoute && <NavBar />}
<div className={authRoute ? "" : "pt-16"}>
<Component {...pageProps} />
</div>
</AuthProvider>
</SWRConfig>
}
export default MyApp
포스트와 관련 라우터 파일인 reddit-clone-app\server\src\routes\posts.ts
에 getPost
핸들러를
아래와 같이 추가한다.
...생략
const getPost = async (req: Request, res: Response) => {
// URL context-path 에서 identifier, slug를 가져옴
const { identifier, slug } = req.params;
try {
// 조건에 충족하는 포스트 데이터 조회
const post = await Post.findOneOrFail({
where: { identifier, slug },
// 관련 테이블 정보 JOIN하여 함께 조회 : subs, votes (Entity의 @JoinColumn 데코레이터 변수 이름을 참조)
relations: ["subs", "votes"]
})
if(res.locals.user) {
// setUserVote : 현재 사용자가 해당 포스트에 가진 투표 value 값을 userVote에 SET
post.setUserVote(res.locals.user);
}
// 포스트 정보 반환
return res.send(post);
} catch (error) {
console.log(error);
return res.status(400).json({ error: "게시물을 찾을 수 없습니다." });
}
}
...생략
const router = Router();
// "/api/posts/:identifier/:slug" 형태의 요청이 들어왔을 때 처리하는 핸들러 = getPost
router.get("/:identifier/:slug", userMiddleware, getPost);
...생략
reddit-clone-app\client\src\pages\r\[sub]\[identifier]\[slug].tsx
파일의
템플릿 return 부분을 아래와 같이 수정한다.
...생략
return (
<div className="flex max-w-5xl px-4 pt-5 mx-auth">
<div className="w-full md:mr-3 md:w-8/12">
<div className="bg-white rounded">
{post && (
<>
<div className="flex">
<div className="py-2 pr-2">
<div className="flex items-center">
<p className="text-xs text-gray-400">
Posted by
<Link href={`/u/${post.username}`}>
<a className="mx-1 hover:underline">
/u/{post.username}
</a>
</Link>
<Link href={post.url}>
<a className="mx-1 hover:underline">
{dayjs(post.createdAt).format("YYYY-MM-DD HH:mm")}
</a>
</Link>
</p>
</div>
<h1 className="my-1 text-xl font-medium">{post.title}</h1>
<p className="my-3 text-sm">{post.body}</p>
<div className="flex">
<button>
<i className="mr-1 fas fa-comment-alt fa-xs">
<span className="font-bold">
{post.commentCount} Comments
</span>
</i>
</button>
</div>
</div>
</div>
</>
)}
</div>
</div>
</div>
)
...생략
최상위 컴포넌트인 reddit-clone-app\client\src\pages\_app.tsx
파일의 템플릿 return 부분을 아래와 같이 수정한다.
Head 노드를 추가하고 font-awesome
CDN 링크를 추가한다.
...생략
return (
<>
<Head>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.4/css/all.css" integrity="sha384-DyZ88mC6Up2uqS4h/KRgHuoeGwBcD4Ng9SiP4dIRy0EXTlnuz47vAwmeGwVChigm" crossOrigin="anonymous"/>
</Head>
<SWRConfig
value=
>
<AuthProvider>
{!authRoute && <NavBar />}
<div className={authRoute ? "" : "pt-16"}>
<Component {...pageProps} />
</div>
</AuthProvider>
</SWRConfig>
</>
)
...생략