NextJs (2) CSR vs SSR(+hydrate, use client)

์ฑ„์˜๋ฏผยท2024๋…„ 2์›” 14์ผ

๐Ÿ“š NestJs์—์„œ ๋ Œ๋”๋ง?

๐Ÿ’ก NestJs๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐฉ์‹

๋ Œ๋”(render) ๋ž€? : nextjs๊ฐ€ ์šฐ๋ฆฌ์˜ react component๋ฅผ ๊ฐ€์ ธ์™€์„œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” html๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์„ ์˜๋ฏธํ•จ

๐Ÿ“š CSR

๐Ÿ’ก ํ‰๋ฒ”ํ•œ react๊ฐ€ ๋ Œ๋”๋˜๋Š” ๋ฐฉ์‹์€ Client Side Rendering : ์ฆ‰ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ง์ ‘ ๋ Œ๋”๋ง์„ ํ•˜๋Š” ๊ณผ์ •,

ํ‰๋ฒ”ํ•œ react๋งŒ์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ƒ์„ฑํ•˜๋ฉด ๊ทธ๊ฒƒ์€ Client Side Application์ด ๋จ.

CSR ํŠน์ง•

  • ์œ ์ €๊ฐ€ ํŽ˜์ด์ง€์— ๋„์ฐฉํ•œ ์‹œ์ ์— ํŽ˜์ด์ง€๋Š” ๋นˆ ํ™”๋ฉด์ž„. ๊ทธ ์ดํ›„ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ชจ๋“  JSํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ๋‚œ ํ›„์—์•ผ UI๊ฐ€ ๊ทธ๋ ค์ง€๊ธฐ ์‹œ์ž‘ํ•จ. (์ฆ‰ CSR์—์„œ UI๋Š” ๋ธŒ๋ผ์šฐ์ €์˜ JS์—”์ง„์— ์˜ํ•ด ๊ทธ๋ ค์ง€๊ฒŒ ๋œ๋‹ค.)
  • ๊ทธ๋ž˜์„œ ์ƒˆ๋กœ๊ณ ์นจ๋“ฑ ํŽ˜์ด์ง€๋ฅผ ๋‹ค์‹œ ๊ทธ๋ฆฌ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ธฐ๋ฉด, UI๊ฐ€ ์‚ญ์ œ๋˜ ์ผ์‹œ์ ์œผ๋กœ ํ™”๋ฉด์— ๋‚ด์šฉ์ด ์‚ญ์ œ ๋จ.
  • SEO (๊ฒ€์ƒ‰์—”์ง„์ตœ์ ํ™”)๋ฅผ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” google์—๊ฒŒ ๋นˆ ํŽ˜์ด์ง€๋ฅผ ๋ณด์—ฌ์ฃผ์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹์ง€๋งŒ (Google์€ ํŽ˜์ด์ง€์˜ HTML์„ ๋ณด๊ธฐ๋•Œ๋ฌธ) ๊ฒ€์ƒ‰ ์—”์ง„์ด ๋ณด๊ฒŒ ๋˜๋Š” ์›น์‚ฌ์ดํŠธ๋Š” ๋น„์–ด์žˆ๊ฒŒ ๋จ.

๐Ÿ“š SSR

๐Ÿ’ก Nestjs์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”ํ•˜๋Š” ๋ฐฉ์‹์€ Server Side Rendering : ํŽ˜์ด์ง€์˜ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ๋ณด๊ฒŒ ๋˜๋ฉด ๋ฆฌ์•กํŠธ์™€๋Š” ๋‹ค๋ฅด๊ฒŒ ํŽ˜์ด์ง€์˜ ๋‚ด์šฉ๋“ค์ด ๋ชจ๋‘ ์‹ค์ œ๋กœ ๋ธŒ๋ผ์šฐ์ € ์ฝ”๋“œ์— ์žˆ์Œ. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ๋ธŒ๋ผ์šฐ์ €๋Š” HTML์„ ํ™”๋ฉด์— ๊ทธ๋ฆฌ๊ธฐ ์œ„ํ•ด JS๊ฐ€ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆด ํ•„์š”๊ฐ€ ์—†๊ฒŒ ๋จ.

NestJs์—์„œ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋“ค์€ ์šฐ์„ ์ ์œผ๋กœ Server Side Rendering ๋˜๊ฒŒ ๋จ.

SSR ํŠน์ง•

  • UI๋ฅผ ๋นŒ๋“œํ•˜๋Š”๋ฐ React๋ฅผ ์“ธ ํ•„์š”๊ฐ€ ์—†์Œ. ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์™€ ํŽ˜์ด์ง€๋“ค์€ ์šฐ์„  ๋ฐฑ์—”๋“œ์—์„œ ๋ฏธ๋ฆฌ ๋ Œ๋”๋˜์–ด์ง.
  • ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์‚ฌ์šฉ์ž๋“ค์ด ํŽ˜์ด์ง€์— ์ ‘๊ทผํ•ด์„œ ๋ฐ”๋กœ UI๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค๋ผ๋Š” ์žฅ์ ์ด ์žˆ์Œ.

๐Ÿ’ก Hydration : Hydrate๋Š” ๋ฐฑ์—”๋“œ์—์„œ ๋ Œ๋”๋ง๋œ ๋‹จ์ˆœ HTML๊ณผ ๋ฒˆ๋“ค๋ง๋œ JS๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ณด๋‚ธ ๋’ค, ํด๋ผ์ด์–ธํŠธ์ธก์—์„œ ๋‹จ์ˆœ HTML์„ ๋ฆฌ์•กํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๊ณผ์ •์„ ์˜๋ฏธ.

Link ์ปดํฌ๋„ŒํŠธ ์˜ˆ์‹œ:
1. Link์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐฑ์—”๋“œ์—์„œ๋Š” ๋‹จ์ˆœํ•œ anchorํƒœ๊ทธ๋กœ ๋ณ€ํ™˜ํ•ด ๋†“๊ธฐ์—, ๋ฐฑ์—”๋“œ์— ๋ Œ๋”๋ง ๋œ ์ˆœ๊ฐ„์—” html์— anchorํƒœ๊ทธ๋กœ UI๋ฅผ ๊ทธ๋ฆฌ๊ฒŒ ๋จ.
2. ์ดํ›„, ์œ ์ €๊ฐ€ ํŽ˜์ด์ง€์— ๋„๋‹ฌํ•œ ์ดํ›„์—๋Š” ๋ฆฌ์•กํŠธ, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋กœ๋“œ๋˜๋ฉฐ ์šฐ๋ฆฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ React App์ด ๋จ.
3. ์• ํ”Œ๋ฆฌํ‚ค์ด์…˜์€ interactiveํ•ด์ง€๊ณ  ๊ทธ๋กœ ์ธํ•ด Link ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ†ตํ•ด ์ƒˆ๋กœ๊ณ ์นจ ์—†์ด ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š” Client Side Navigation์„ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋จ.

๐Ÿ—ฃ๏ธ ๊ทธ๋Ÿฌ๋‚˜ Hydration ์€ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์— ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ.

๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋“ค์€ server side์—์„œ ๋จผ์ € ๋ Œ๋”๋˜์ง€๋งŒ ๊ทธ ์ค‘ client์—์„œ interactiveํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์งˆ ์ปดํฌ๋„ŒํŠธ๋“ค์€ ์˜ค์ง use client ์ง€์‹œ์–ด๋ฅผ ๋งจ ์œ„์— ๊ฐ–๊ณ  ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ ๋“ค ๋ฟ์ž„.

  • use client : use client๋Š” ๋ฐฑ์—”๋“œ์—์„œ ๋ Œ๋”๋˜๊ณ  ํ”„๋ก ํŠธ์—”๋“œ์—์„œ Hydrate๋จ์„ ์˜๋ฏธ ์˜ค์ง client์—์„œ๋งŒ ๋ Œ๋”๋œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹˜.
  • ํ”„๋ ˆ์ž„์›Œํฌ์—๊ฒŒ ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ interactiveํ•ด์ง€๊ณ  ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๋Š” ๋‹จ์ˆœํ•œ html์ž„์„ ๋ฏธ๋ฆฌ ์•Œ๋ ค์ฃผ๋ฉด ์ด๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค์šด๋กœ๋“œ๋ฐ›์„ javascript ์–‘์ด ์ ์–ด์ง์„ ์˜๋ฏธํ•จ.
  • client component : use client ํ‚ค์›Œ๋“œ๋ฅผ ์ตœ์ƒ๋ถ€์— ์„ ์–ธํ•œ ์ปดํฌ๋„ŒํŠธ๋“ค
  • server component์—์„œ client component๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ๊ฐ€๋Šฅํ•จ, client component์—์„œ server component๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ๋ถˆ๊ฐ€๋Šฅํ•จ.
  • client component์—์„œ ํ˜ธ์ถœ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค์€ client component๊ฐ€ ๋จ. (2024.2์›” ๊ธฐ์ค€)

โœ… ์ •๋ฆฌ

    1. ๋ชจ๋“  ๊ฒƒ์€ ์šฐ์„ ์ ์œผ๋กœ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ pre render ๋˜์–ด html ๋กœ ๋ณ€ํ™˜๋จ.
    1. ๊ทธ ํ›„์— ์˜ค์ง client components ๋งŒ์ด Hydrate ๋˜์–ด interactiveํ•˜๊ฒŒ ๋จ.

๐Ÿ“š 'Use Client' ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ… (2024๋…„ 2์›” 16์ผ)

  • ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ Use Client๋ฅผ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ, ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.
// components/movie-video.tsx
"use client";

import styles from "../styles/movie-videos.module.css";
import { API_URL } from "../app/(home)/page";

async function getVideos(id: string) {
	const response = await fetch(${API_URL}/${id}/videos);
	return response.json();
}

export default async function MovieVideos({ id }: { id: string }) {
	const videos = await getVideos(id);

	return (
		<div className={styles.container}>
		{videos.map((video) => (
			<iframe
				key={video.id}
				src={https://youtube.com/embed/${video.key}}
				title={video.name}
				/>
		))}
		</div>
	);
}

์—๋Ÿฌ : Error: ร— You are attempting to export "metadata" from a component marked with "use client", which is disallowed. Either remove the export, or the "use client" directive.

(์—๋Ÿฌ ๋ฉ”์„ธ์ง€ ์ „๋ฌธ)	Error: ร— You are attempting to export "metadata" from a component marked with "use client", which is disallowed. Either remove the export, or the "use client" directive. Read more: https://nextjs.org/ โ”‚ docs/getting-started/react-essentials#the-use-client-directive โ”‚ โ”‚ โ•ญโ”€[/Users/chaeyeongmin/Desktop/JavaScript/Next.js/nextjs14/app/(home)/page.tsx:2:1] 2 โ”‚ import Movie from "../../components/movie"; 3 โ”‚ import styles from "../../styles/home.module.css"; 4 โ”‚ 5 โ”‚ export const metadata: Metadata = { ยท โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 6 โ”‚ title: "Home", 7 โ”‚ }; โ•ฐโ”€โ”€โ”€โ”€ Import trace for requested module: ./app/(home)/page.tsx ./components/movie-videos.tsx    
  • ์œ„ ๋ฉ”์„ธ์ง€์— ๋”ฐ๋ฅด๋ฉด ๋‹ค์Œ ํŒŒ์ผ์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค๊ณ  ํ•œ๋‹ค.
import { Metadata } from "next";
import Movie from "../../components/movie";
import styles from "../../styles/home.module.css";

export const metadata: Metadata = {
  title: "Home",
};

export const API_URL = "https://nomad-movies.nomadcoders.workers.dev/movies";

async function getMovies() {
  const response = await fetch(API_URL);
  const json = await response.json();
  return json;
}

export default async function HomePage() {
  const movies = await getMovies();
  return (
    <div className={styles.container}>
      {movies.map((movie) => (
        <Movie
          key={movie.id}
          id={movie.id}
          poster_path={movie.poster_path}
          title={movie.title}
        />
      ))}
    </div>
  );
}
  • ๐Ÿ—ฃ๏ธ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์–ด๋ณด๋‹ˆ, ์ง€์†์ ์œผ๋กœ metadata๋ฅผ exportํ•˜๋Š” ํ–‰์œ„๋ฅผ ์ง€์ ํ•˜๊ณ  ์žˆ์–ด์„œ ๋‚˜๋Š” metadata๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ž˜๋ชป๋œ ์ค„ ์•Œ์•˜๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์œ„์˜ ๋‘ ํŒŒ์ผ์—์„œ metadata๋Š” ์ „ํ˜€ ์—ฐ๊ด€์ด ์—†์—ˆ๊ณ , ๊ฒฐ๊ตญ ๋ฌธ์ œ์˜ ์›์ธ์€ 'use client' ์ง€์‹œ์–ด์˜ ํŠน์„ฑ์— ์žˆ์—ˆ๋‹ค. 'use client' ์ง€์‹œ์–ด๊ฐ€ ๋ถ™์€ ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ์–ด๋– ํ•œ ๋ณ€์ˆ˜๋„ exportํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ์ด ์›์น™์ด๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋ฅผ ์œ„๋ฐ˜ํ–ˆ๋˜ ๊ฒƒ์ด ๋ฌธ์ œ์˜€๋‹ค. ์ด๋Ÿฌํ•œ ์›๋ฆฌ๋ฅผ ์ดํ•ดํ•˜๊ณ  ๋‚˜๋‹ˆ, API_URL์„ ๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋”ฐ๋กœ ์„ ์–ธํ•จ์œผ๋กœ์จ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

  • ๐Ÿ’ก ์˜ค๋ฅ˜ ํ•ด๊ฒฐ

  • ๋‹ค์Œ๊ณผ ๊ฐ™์ด import๋กœ ์ฐธ์กฐํ•ด์˜ค๋˜ API_URL์„ ํ•ด๋‹น ํŒŒ์ผ์•ˆ์—์„œ ๋ถˆ๋Ÿฌ์ฃผ์—ˆ๋‹ค.
"use client";

import styles from "../styles/movie-videos.module.css";

async function getVideos(id: string) {
  const API_URL = "https://nomad-movies.nomadcoders.workers.dev/movies";
  const response = await fetch(`${API_URL}/${id}/videos`);
  return response.json();
}

export default async function MovieVideos({ id }: { id: string }) {
  const videos = await getVideos(id);

  return (
    <div className={styles.container}>
      {videos.map((video) => (
        <iframe
          key={video.id}
          src={`https://youtube.com/embed/${video.key}`}
          title={video.name}
</iframe>
      ))}
    </div>
  );
}
profile
ํ•ญ์ƒ ์žฌ๋ฐŒ๋Š”๊ฒƒ์„ ์ฐพ์•„ ํ—ค๋งค๋Š” ํ˜ธ๋ž‘์ด๋  ๋– ๋Œ์ด;

0๊ฐœ์˜ ๋Œ“๊ธ€