[장점]
[단점]
[장점]
[단점]
[장점]
[단점]
💡next.js앱을
yarn dev (npm run start)
로 실행하는 경우 아직 build 되지 않은 상태의 앱이기때문에 SSG로 구현 된 코드라도 SSR방식으로 작동
$ yarn create next-app (앱이름) --js
프로젝트 생성 시 import alias가 기본적으로 설정되어, import 경로로 @
(기본값)를 사용할 수 있다. ( @
는 src/
폴더를 뜻함 )
yarn dev
로 프로젝트를 실행시키면 src/pages
폴더 내부의 index.js가 실행 됨
index.js
는 실행 시 _app.js
와 _document.js
를 차례로 거친 뒤 실행
_app.js
_document.js
폴더경로 Route 비고 pages/post/index.jsx /post index파일은 해당파일의 폴더명으로 경로가 연결됨 pages/post/detail.jsx /post/detail index.jsx를 제외한 파일명은 폴더명의 루트 뒤에 연결 됨 pages/post/[id].jsx /post/${id} - 동적으로 경로 생성
- useRouter() 를 사용해서 경로의 id값을 가져올 수 있음pages/post/[…params].jsx /post/*/*/* - 동적으로 경로 생성
- 경로의 깊이와 상관없이 모든 경로로 접근 가능
- useRouter()를 통해 각 경로를 배열형태로 받아올 수 있음경로 우선순위
detail(경로명 직접명시) > [id](하나의 속성 명시) > [...params](나머지 경로)
import { useRouter } from 'next/router'
const router = useRouter()
// src/pages/post/[id].jsx
// 접근경로 '/post/1?name=hello'
import { useRouter } from 'next/router'
const router = useRouter()
const {id, name} = router.query
console.log(id) //'1'
console.log(name) //'hello'
// src/pages/user/[...params].jsx
// 접근경로 '/user/123/abc'
import { useRouter } from 'next/router'
const router = useRouter()
const { params } = router.query
console.log(params) //['123', 'abc']
<Link>
태그를 사용하지 않고 함수내에서 페이지 전환을 하고 싶다면 router.push()
사용router.push(url)
<button onClick={() => router.push('/post/1')}>1번 글로가기</button>
<button onClick={() => router.push({ pathname: '/post/[id]', query: { id: 1 } })}>1번 글로가기</button>
getStaticProps()
를 사용한 페이지)의 경우 query가 빈 객체로 전달 된 뒤에 채워짐router.isReady
를 사용하여, query가 준비 되면 그때 query 데이터에 접근 하도록 함useEffect(() => {
if (router.isReady) console.log(router.query)
}, [router.isReady])
<a>
태그 대신 <Link>
태그를 사용해야 함import Link from "next/link";
// 기본방식
<Link href='/post'>post로 이동</Link>
// 쿼리파라미터
<Link href='/post?name=hello'>post로 이동</Link>
// url객체
<Link href={{ pathname: '/post', query: { name: 'hello' } }}>post로 이동</Link>
성능최적화를 위해 기존의 <img />
태그가 아닌 <Image />
태그를 사용
Image태그 특징
import Image from 'next/image';
<Image
src="/images/profile.jpg" // 이미지 주소, 경로
height={200} // CLS 방지를 위해 사용됨
width={200} // CLS 방지를 위해 사용됨
alt="profile"
/>
position: relative
및 display: block
속성을 가지고 있어야 함<Image src="/images/profile.jpg" alt="profile" fill />
<Image src={dogImage} alt="dog" fill sizes="(max-width: 768px) 33vw, (max-width: 1200px) 50vw, 100vw"/>7
/* 화면 크기가 768px 보다 작다면 뷰포트 가로길이의 33% 정도의 크기를 가진 이미지를 공급해주고,
1200px 보다 작다면 뷰포트 가로길이의 50% 정도의 크기를 가진 이미지를 공급해주고,
그것보다 크다면 100% 크기를 가진 이미지를 공급하도록 설정*/
//next.config.js
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'via.placeholder.com', // 전부 허용하고자 할 경우에는 **
port: '',
pathname: '**', // 전부 허용하고자 할 경우에는 **
},
],
},
Prop ‘className’ did not match
라는 경고가 발생하며 스타일이 적용되지 않음.import styled from 'styled-components'
export const Container = styled.div`
display: flex;
flex-direction: column;
`
function Post() {
return (
<Container>
<h1>Post</h1>
</Container>
)
}
export default Post
// ERROR! Prop ‘className’ did not match
styled-components의 경우 사용자에게 페이지를 공급 할 때 마다 새로 고유한 classname을 설정하는데, next.js에서 build시에 미리 렌더링 한 정적 페이지의 classname과 서버에서 사용자에게 공급하며 렌더링 된 페이지간에 classname이 일치하지 않기때문
next.config.js
에 compiler추가
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
compiler: {
styledComponents: true
}
}
module.exports = nextConfig
styledComponents: true
설정 시 최초 빌드할 때와 서버/클라이언트에서 렌더링할 때의 className 을 일치시킴 getStaticProps()
함수를 만들어 필요한 데이터를 호출한 뒤, props
에 객체형태로 담아 returngetStaticProps()
에서 return한 데이터는 컴포넌트에서 props로 받을 수 있다//기본형태
export async function getStaticProps() {
// 데이터 호출
return { props: { 데이터 } }
}
function 페이지컴포넌트({ 데이터 }) {
return <div></div>
}
import axios from 'axios'
export async function getStaticProps() {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts')
const posts = response.data
return { props: { posts } }
}
function Post({ posts }) {
return (
<div>
{posts.map((post) => (
<div key={post.id}>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
))}
</div>
)
}
export default Post
revalidate: 10 // 단위: 초(s)
revalidate
옵션을 사용하면 설정한 시간이 지날 때 마다 주기적으로 다시 build하여 데이터를 새로 불러옴ISR(Incremental Static Regeneration)
방식이라 함// 예시
export async function getStaticProps() {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
);
const posts = response.data;
return { props: { posts }, revalidate: 10 }; //10초에 한번씩 다시 build
}
동적인 경로를 사용하는 페이지에서, next.js는 사전에 어떤 페이지 경로를 미리 렌더링 해두어야 하는지 알 수 없음
getStaticPaths()
는 동적인 경로를 사용하는 페이지에서 특정한 경로를 미리 렌더링 해둘 수 있도록 지정
getStaticPaths()
의 return 값을 통해 지정이 가능하며, 배열형식으로 경로를 담아 설정
예를들어 /posts/[id]
의 동적경로로 접근하는 페이지가 있을 때, 아래와 같이 코드를 작성하면, posts/1
, posts/2
, posts/3
페이지를 build시에 정적으로 생성
export async function getStaticPaths() {
return {
paths: [
{params: {id: '1'}}, {params: {id: '2'}}, {params: {id: '3'}}...
]
}
}
export async function getStaticPaths() {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
);
const posts = response.data;
const paths = posts.map((post) => ({ params: { id: `${post.id}` } }));
return { paths, fallback: false };
}
1~100번글만 있는 상황에 사용자가 101번 게시글에 접근하는 경우
fallback: false
=> 404페이지로 이동fallback: true
=> 그제서야 해당 id로 요청을 보내고 응답을 가져옴 (로딩화면 설정가능)fallback: blocking
=> 그제서야 해당 id로 요청을 보내고 응답을 가져옴 (로딩화면 없이, 로딩이 완료되어야 페이지를 보여줌)true
나 blocking
로 옵션값을 설정하는 경우 사용자가 한번 접근한 페이지는 캐싱되어 재사용
true
설정시 보여 줄 로딩페이지는 router.isFallback
으로 boolean값을 받아와 아래처럼 작성
const router = useRouter()
if (router.isFallback) {
return <div>로딩 중..</div>
}
getStaticPaths()
로 생성하게 되는 페이지에서 외부데이터를 사용해야 하는 경우 두 메소드를 함께 사용한다getStaticPaths()
에서 return한 경로데이터를 getStaticProps()
로 받아서 사용 할 수 있다getStaticPaths()
에서 return한 paths배열의 길이만큼 getStaticProps()
가 실행코드 흐름:
getStaticPaths()
> paths전달 >getStaticProps()
> props전달 =>페이지컴포넌트()
import axios from "axios";
// getStaticPaths => 지금 접근 할 아이디 번호 명시
export async function getStaticPaths() {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
);
const posts = response.data;
const paths = posts.map((post) => ({ params: { id: `${post.id}` } }));
// paths = [{params: {id: 1}}, {params: {id: 2}}, {params: {id: 3}} ...]
return { paths };
}
// getStaticProps => getStaticPaths가 리턴한 번호를 토대로 요청을 보내고, 데이터를 저장
export async function getStaticProps({ params }) {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/posts/${params.id}`
);
const post = response.data;
return { props: { post } };
}
// 페이지컴포넌트 => getStaticProps가 리턴한 데이터를 props로 받아서 사용
function PostDetail({ post }) {
return (
<div>
<span>{post.id}</span>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
}
export default PostDetail;
getServerSideProps()
함수를 사용하며 사용 방식은 getStaticProps()
와 같음import axios from 'axios'
export async function getServerSideProps() {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts')
const posts = response.data
return { props: { posts } }
}
function Post({ posts }) {
return (
<div>
{posts.map((post) => (
<div key={post.id}>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
))}
</div>
)
}
export default Post
💡정말 업데이트가 잦은 경우가 아니라면, SSR 방식 보다는 SSG 방식에 revalidate 옵션을 명시하여 사용하는것을 추천