#typescript #next-js #tailwindcss

포스트 페이지 생성하기

Nov 14, 2022


포스트 페이지 결과 UI


포스트 페이지 생성

포스트 페이지의 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를 재사용

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

getPost(포스트 정보) API 생성

포스트와 관련 라우터 파일인 reddit-clone-app\server\src\routes\posts.tsgetPost 핸들러를
아래와 같이 추가한다.

...생략
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);
...생략

포스트 페이지 UI

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>
    )
...생략

font-awesome CDN 적용하기

최상위 컴포넌트인 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>
        </>
    )
...생략

포스트 페이지 UI 결과



*****

© 2021, Ritij Jain | Pudhina Fresh theme for Jekyll.