해당 포스트는
blegram
이라는 솔로 프로젝트를 만들면서 사용했던Next.js 13
의SSR
과Fetch API
에 대한 내용을 기록하는 포스트입니다.
아직 Next.js 13에 대해 공부가 부족한 부분이 많아서 잘못된 내용이 포함되어있을 확률이 높습니다…🥲
지금 기록해두지 않으면 나중에 지금의 경험을 잊을 것 같아서 기록하는 용도로 작성했습니다.
🔥 사용
0️⃣ “use client”
Next.js 13
에서는 기본적으로 컴포넌트를 SSR
을 하고 "use client"
을 최상위에 작성한 컴포넌트는 CSR
을 합니다.
CSR
을 적용해야하는 컴포넌트는 클라이언트에서만 사용할 수 있는 기능을 사용하는 컴포넌트입니다.
대표적으로 use
를 붙이는 훅을 사용하는 컴포넌트입니다.
처음에는 이 부분을 몰라서 모든 페이지 컴포넌트에 "use client"
를 붙였습니다.
그렇게 하면 metadata
를 사용할 수 없기 때문에 어떻게 해야하는지 몰라서 일단 넘어갔다가 이후에 모두 수정했습니다.
페이지 컴포넌트에서 CSR
이 필요한 컴포넌트들은 또 따로 컴포넌트로 제작하고 상단에 "use client"
를 붙이는 방식으로 작성해야 합니다.
추가적으로 "use client"
가 붙은 컴포넌트의 하위 컴포넌트는 모두 CSR
이 됩니다. 즉, "use client"
가 있는 것과 같이 처리됩니다.
1️⃣ Fetch API와 Axios
13이전에는 <Head>
와 getStaticProps()
, getServerSideProps()
와 같은 방법으로 SSR
을 적용했는데 13부터는 페이지 컴포넌트 ( "use cleint"
를 붙이지 않은 컴포넌트 ) 자체에 Fetch API
를 사용하면 됩니다.
no-cache
, force-cache
, next: { validate: 60 }
과 같은 옵션 값을 이용해서 이전에 사용했던 메서드 각자의 특성을 적용할 수 있습니다.
- 알아야 할 것
- 구체적인
URL
을 지정해줘야 함 axios
는 사용하기 불편함 (fetch
처럼 타입이 적용되지 않음 )- 기본적으로 같은 요청이라면
fetch()
를 여러 번 사용해도 캐싱된 데이터를 사용함 ( 기본 값force-cache
)
- 구체적인
- /src/app/page.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/** 2023/05/07 - 게시글들 가져오기 요청 - by 1-blue */
const fetchPosts: ApiFetchPostsHandler = async ({ take, lastIdx = -1 }) =>
fetch(process.env.BASE_URL + `/api/posts?take=${take}&lastIdx=${lastIdx}`, {
cache: "no-cache",
}).then((res) => res.json());
interface Props {
searchParams: { postIdx: string | undefined };
}
/** 2023/04/30 - 메타데이터 - by 1-blue */
export const generateMetadata = async ({
searchParams: { postIdx },
}: Props): Promise<Metadata> => {
// 서버 사이드에서 실행됨
const data = await apiServiceSSR.fetchPosts({
take: 10,
lastIdx: Number(postIdx) || -1,
});
return getMetadata({
title: "메인",
description: data?.posts?.[0].content || "게시글이 존재하지 않습니다.",
images: data.posts?.[0].photos[0]
? [combinePhotoURL(data.posts[0].photos[0])]
: undefined,
});
};
/** 2023/03/24 - 홈 페이지 - by 1-blue ( 2023/04/09 ) */
const HomePage = async ({ searchParams: { postIdx } }: Props) => {
// 서버 사이드에서 실행됨
const initialData = await apiServiceSSR.fetchPosts({
take: 10,
lastIdx: Number(postIdx) || -1,
});
// 서버 사이드에서 패치한 데이터를 클라이언트 사이드의 컴포넌트로 내려줌
return <Post initialData={initialData} />;
};
export default HomePage;
2️⃣ metadata
메타데이터에 대한 내용이 너무 많아서 작성하면 좋을 것 같은 부분만 작성했습니다.
매번 페이지 컴포넌트에 작성하기에는 반복이 많아서 모듈로 분리했습니다.
( styled-components
를 적용하다보니 layout.tsx
가 클라이언트 사이드 컴포넌트가 되어서 각자에서 적용 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import type { Metadata } from "next";
/** 2023/04/30 - 메타 데이터 기본 형태 - by 1-blue */
export const defaultMetadata: Metadata = {
title: "blegram",
description: "인스타그램 클론 사이트입니다. ( by 1-blue )",
generator: "Next.js",
applicationName: "blegram",
referrer: "origin-when-cross-origin",
colorScheme: "dark",
authors: [{ name: "1-blue", url: "https://github.com/1-blue" }],
creator: "1-blue",
keywords: [
"1-blue",
"TypeScript",
"Next.js",
"react-query",
"prisma",
"aws-s3",
"aws-rds, instagram",
"인스타그램 클론",
"front-end, 프론트엔드",
"신입 개발자",
"개발자",
],
openGraph: {
title: "blegram",
description: "인스타그램 클론 사이트입니다. ( by 1-blue )",
siteName: "blegram",
images: ["/logo.png"],
locale: "ko-KR",
type: "website",
},
twitter: {
card: "summary_large_image",
title: "blegram",
description: "인스타그램 클론 사이트입니다. ( by 1-blue )",
creator: "1-blue",
images: ["/logo.png"],
},
};
// type
interface GetMetadataProps {
title: string;
description: string;
images?: string[];
url?: string;
}
/** 2023/05/07 - 메타데이터 제조기 - by 1-blue */
export const getMetadata = ({
title,
description,
images,
url,
}: GetMetadataProps): Metadata => ({
...defaultMetadata,
title: "blegram | " + title,
description: description + "\n" + "인스타그램 클론 사이트 ( by 1-blue )",
// og
openGraph: {
...defaultMetadata.openGraph,
title: "blegram | " + title,
description: description + "\n" + "인스타그램 클론 사이트 ( by 1-blue )",
images: images ? images : ["/logo.png"],
url: process.env.BASE_URL + url,
},
// twitter
twitter: {
...defaultMetadata.twitter,
title: "blegram | " + title,
description: description + "\n" + "인스타그램 클론 사이트 ( by 1-blue )",
images: images ? images : ["/logo.png"],
},
});
💀 문제
0️⃣ SSR과 cookie
현재 로그인 방식이 cookie
에 JWT
를 동봉해서 처리를 하고 있습니다.
근데 SSR
을 적용하면 서버측에서 실행되다보니 브라우저에 존재하는 cookie
를 자동으로 넣어서 보내주지 않아서 유저 정보가 필요한 요청에 대해서 잘못된 정보를 내려받게 됩니다.
Fetch API
에 쿠키를 강제적으로 넣어보려고 했는데 안들어가서 어떻게 해야할지 모르겠어서 일단은 클라이언트에서 다시 같은 요청을 보내도록 처리했습니다.
header
에 authorization
으로 토큰을 직접 넣어서 주는 방식으로는 토큰이 전달되는 것을 확인했는데 이미 cookie
를 이용해서 처리하는 로직을 모두 구성해놔서 다른 로직을 추가하기 보다는 쿠키를 강제로 넣는 방법으로 처리하고 싶어서 기록만 해두고 넘어가려고 합니다.
마지막 배포할 때까지 방법을 못 찾으면 header
를 통해서 전달하도록 처리하려고 합니다 🥲
1️⃣ og:url
원래는 metadata.openGraph.url
을 사이트의 기본 URL
로 지정했었습니다.
이렇게 적용하고 kakao developer 공유 디버거에서 확인해보니 https://blegram.vercel.app
이던 https://blegram.vercel.app/post?postIdx=5
이던 즉 루트 경로 + /
뒤에 어떤 경로가 와도 자꾸 루트 경로의 metadata
를 가져와서 사용하게 되는 문제가 발생했습니다.
어디서 메인으로 리다이렉트가 되는지 아니면 다른 문제인지 하나씩 바꿔가면서 배포하고 테스트하고를 반복하다보니 og:url
에서 루트 경로로 설정해서 발생된 문제인 것을 확인했습니다.
og:url
에는 페이지를 대표하는 URL
을 적으면 되는 것으로 알고 있었는데 그게 아니라 해당 페이지의 URL
을 적어야 하네요… 🥲