(출처 : 내일배움캠프 Next.js 특강 노션)
npx create-next-app@latest
import React from "react";
const TestPage = () => {
return <div>TestPage</div>;
};
export default TestPage;
import React from "react";
// ⭐️⭐️⭐️ props로 params를 가져오는것 참고!!
const TestDetailPage = ({ params }: { params: { id: string } }) => {
return <div>TestDetailPage : {params.id}</div>;
};
export default TestDetailPage;
(폴더명)
: 이 폴더는 경로로 포함하지 않겠다!import React from "react";
const AdminAboutPage = () => {
return <div>AdminAboutPage</div>;
};
export default AdminAboutPage;
import React from "react";
// ⭐️ children props로 받기
const MarketingLayout = ({ children }: { children: React.ReactNode }) => {
return (
<div>
// 공통적으로 보여지는 부분은 children 외 나머지 부분
<p>여기는 마케팅과 관련된 페이지가 보이는 곳입니다!</p>
// ⭐️ children 보여주기
{children}
</div>
);
};
export default MarketingLayout;
ex 1) localhost3000/business
ex 2) localhost3000/contact
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
// ⭐️ 모든 경로에서 보일 부분
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
<a href="/blog">Blog</a>
</nav>
// ⭐️ children - 하위 경로들이 전부 보일 부분
{children}
</body>
</html>
);
}
📝 layout과 유사한 컴포넌트인 template.tsx!
- layout : 경로 전반에 걸쳐서 상태가 유지됨 (최초 렌더링시 이후에는 바뀌지 x)
- template : 동일한 Template을 공유하는 경로 사이를 왔다갔다 할 때 DOM 요소가 다시 생성됨
- open animation : template이 적합
metadata
의 title에서 수정할 수 있다. & 다른 경로마다 다른 metadata title을 지정할 수도 있다! (각 경로의 layout 혹은 각 컴포넌트마다 설정 가능)export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
"use client"
삽입 필요const router = useRouter(); //⭐️ import는 next/navigation에서!
// 생략
// ⭐️ router.push("경로")
<li onClick={()=>{ router.push("/test/2") }}>테스트</li>
// 생략
router.push("")
: history stack이 쌓임router.replace("")
: history stack이 없어짐(모든 history 초기화)Next.js 버전 12까지 : 렌더링 방식을 '페이지 단위'로 규정
Next.js 버전 13 이후 : '컴포넌트 단위'로 규정
Next.js 13버전부터 컴포넌트들은 default로 서버 컴포넌트
다!!
"use client";
표시 없으면 전부 서버 컴포넌트라는 뜻!
📝 어떨때 클라이언트 컴포넌트("use client")로 써야하고, 어떨때 서버 컴포넌트로 써야할까?
- user와의 상호작용이 있는 경우 :
클라이언트 컴포넌트
(ex. onClick, useState 등 사용할 때, counter 앱, 등등)- 그 외 :
서버 컴포넌트
// ✅ 예시 코드
import Image from "next/image";
import React from "react";
import type { RandomUser } from "@/types";
// SSG TEST : 아무것도 하지 않으면 SSG!
// ⭐️ 리액트 컴포넌트인데도 async 부여 가능! (Next.js니까!)
// 클라이언트 컴포넌트에서는 async 못붙여줌 (use client 붙이면 오류남)
const SSG = async () => {
// (1) 첫 번째 방법 : fetch에 아무 옵션도 부여 x
const response = await fetch(`https://randomuser.me/api`);
const { results } = await response.json();
const user: RandomUser = results[0];
return (
<div className="mt-8">
<div className="border p-4 my-4">
<div className="flex gap-8">
{/* 유저 기본정보 */}
<div>
<Image
src={user.picture.large}
alt={user.name.first}
width={200}
height={200}
/>
<h2 className="text-xl font-bold">
{user.name.title}. {user.name.first} {user.name.last}
</h2>
<p className="text-gray-600">{user.email}</p>
<div className="mt-4">
<p>
<span className="font-bold">전화번호 : </span>
{user.phone}
</p>
<p>
<span className="font-bold">휴대전화번호 : </span>
{user.cell}
</p>
<p>
<span className="font-bold">사는 곳 : </span>
{user.location.city}, {user.location.country}
</p>
<p>
<span className="font-bold">등록일자 : </span>
{new Date(user.registered.date).toLocaleDateString()}
</p>
<p>
<span className="font-bold">생년월일 : </span>
{new Date(user.dob.date).toLocaleDateString()}
</p>
</div>
</div>
{/* 지도영역 */}
<iframe
src={`https://maps.google.com/maps?q=${user.location.coordinates.longitude},${user.location.coordinates.latitude}&z=15&output=embed`}
height="450"
width="600"
></iframe>
</div>
</div>
</div>
);
};
export default SSG;
// ✅ 예시 코드 1. fetch에 옵션주기 - ISR.tsx에서
import Image from "next/image";
import React from "react";
import type { RandomUser } from "@/types";
const ISR = async () => {
const response = await fetch(`https://randomuser.me/api`, {
// ⭐️ 5초에 한번씩 데이터 업데이트하라고 옵션 주기!
next: {
revalidate: 5,
},
});
const { results } = await response.json();
const user: RandomUser = results[0];
return (
<div className="mt-8">
<div className="border p-4 my-4">
<div className="flex gap-8">
{/* 유저 기본정보 */}
<div>
<Image
src={user.picture.large}
alt={user.name.first}
width={200}
height={200}
/>
<h2 className="text-xl font-bold">
{user.name.title}. {user.name.first} {user.name.last}
</h2>
<p className="text-gray-600">{user.email}</p>
<div className="mt-4">
<p>
<span className="font-bold">전화번호 : </span>
{user.phone}
</p>
<p>
<span className="font-bold">휴대전화번호 : </span>
{user.cell}
</p>
<p>
<span className="font-bold">사는 곳 : </span>
{user.location.city}, {user.location.country}
</p>
<p>
<span className="font-bold">등록일자 : </span>
{new Date(user.registered.date).toLocaleDateString()}
</p>
<p>
<span className="font-bold">생년월일 : </span>
{new Date(user.dob.date).toLocaleDateString()}
</p>
</div>
</div>
{/* 지도영역 */}
<iframe
src={`https://maps.google.com/maps?q=${user.location.coordinates.longitude},${user.location.coordinates.latitude}&z=15&output=embed`}
height="450"
width="600"
></iframe>
</div>
</div>
</div>
);
};
export default ISR;
// ✅ 예시 코드 2. page.tsx 컴포넌트에 revalidate 추가하기 - page.tsx에서
// (ISR.tsx에서 줬던 옵션은 지우고)
// 페이지에 포함된 모든 데이터가 5초마다 업데이트됨
// 페이지 전체가 업데이트되어야 할 때 유용
// src>app>rendering>page.tsx
import ISR from "@/components/rendering/ISR";
import React from "react";
export const revalidate = 5;
const RenderingTestPage = () => {
return (
<div>
<h1>4가지 렌더링 방식을 테스트합니다.</h1>
<ISR />
</div>
);
};
export default RenderingTestPage;
실시간
으로 계속 바뀜// ✅ 예시 코드 - fetch에 'no-cache' 옵션주기!
// src>components>rendering>SSR.tsx
import Image from "next/image";
import React from "react";
import type { RandomUser } from "@/types";
const SSR = async () => {
const response = await fetch(`https://randomuser.me/api`, {
cache: "no-cache",
});
const { results } = await response.json();
const user: RandomUser = results[0];
return (
<div className="mt-8">
<div className="border p-4 my-4">
<div className="flex gap-8">
{/* 유저 기본정보 */}
<div>
<Image
src={user.picture.large}
alt={user.name.first}
width={200}
height={200}
/>
<h2 className="text-xl font-bold">
{user.name.title}. {user.name.first} {user.name.last}
</h2>
<p className="text-gray-600">{user.email}</p>
<div className="mt-4">
<p>
<span className="font-bold">전화번호 : </span>
{user.phone}
</p>
<p>
<span className="font-bold">휴대전화번호 : </span>
{user.cell}
</p>
<p>
<span className="font-bold">사는 곳 : </span>
{user.location.city}, {user.location.country}
</p>
<p>
<span className="font-bold">등록일자 : </span>
{new Date(user.registered.date).toLocaleDateString()}
</p>
<p>
<span className="font-bold">생년월일 : </span>
{new Date(user.dob.date).toLocaleDateString()}
</p>
</div>
</div>
{/* 지도영역 */}
<iframe
src={`https://maps.google.com/maps?q=${user.location.coordinates.longitude},${user.location.coordinates.latitude}&z=15&output=embed`}
height="450"
width="600"
></iframe>
</div>
</div>
</div>
);
};
export default SSR;
실시간
으로 계속 바뀜 22// ✅ 예시 코드 - "use client" 옵션 주기
"use client";
import Image from "next/image";
import React, { useEffect, useState } from "react";
import type { RandomUser } from "@/types";
const CSR = () => {
const [user, setUser] = useState<RandomUser | null>(null);
useEffect(() => {
const fetchUser = async () => {
const response = await fetch(`https://randomuser.me/api`);
const { results } = await response.json();
setUser(results[0]);
};
fetchUser();
}, []);
if (!user) {
return <div>로딩중...</div>;
}
return (
<div className="mt-8">
<div className="border p-4 my-4">
<div className="flex gap-8">
{/* 유저 기본정보 */}
<div>
<Image
src={user.picture.large}
alt={user.name.first}
width={200}
height={200}
/>
<h2 className="text-xl font-bold">
{user.name.title}. {user.name.first} {user.name.last}
</h2>
<p className="text-gray-600">{user.email}</p>
<div className="mt-4">
<p>
<span className="font-bold">전화번호 : </span>
{user.phone}
</p>
<p>
<span className="font-bold">휴대전화번호 : </span>
{user.cell}
</p>
<p>
<span className="font-bold">사는 곳 : </span>
{user.location.city}, {user.location.country}
</p>
<p>
<span className="font-bold">등록일자 : </span>
{new Date(user.registered.date).toLocaleDateString()}
</p>
<p>
<span className="font-bold">생년월일 : </span>
{new Date(user.dob.date).toLocaleDateString()}
</p>
</div>
</div>
{/* 지도영역 */}
<iframe
src={`https://maps.google.com/maps?q=${user.location.coordinates.longitude},${user.location.coordinates.latitude}&z=15&output=embed`}
height="450"
width="600"
></iframe>
</div>
</div>
</div>
);
};
export default CSR;
Rendering 패넌 4가지 구현할 때,
dev
(개발서버)에서 하면 안됨!
(dev모드에서는 SSG로 해놔도 원활한 테스팅을 위해 SSR처럼 동작하기 때문)
->build
한 다음start
해서 열어야 제대로 볼 수 있음