웹페이지 로딩 이전에 하는 렌더링
Next.js에서는 기본적으로 모든 페이지를 정적 생성한다.
프리렌더링이 일어나는 시점은 브라우저가 HTML 받아오기 전이다.
이미 렌더링 된 HTML과 리액트의 데이터를 연결하는 작업
프로젝트를 빌드하는 시점에 미리 HTML을 렌더링하는 것
Next.js에서는 이미 정적 생성을 제공하고 있다.
npm run build
.next/server/pages/index.html
에서
Cmd+Shift+P
,
Format document
-> HTML 코드가 정리 되어서 보인다. (React에서 작성한 코드가 HTML로 정리되어 보임)
개발 모드가 아니라 프로덕션 모드로 실행
npm run start
HTML을 보내준다.
useEffect
에서 실행하는 코드는 프리렌더링 할 때 서버에서 실행되지 않고 웹 브라우저가 다 로딩되고 나서 웹 브라우저에서 실행되는 코드이다.
getStaticProps 이름이랑 export는 정해진 약속이기 때문에 반드시 지켜줘야 한다.
export async function getStaticProps() {
const res = await axios.get('/products');
const products = res.data.results;
return{
props:{
products,
};
}
export default function Home({products}) {
return (
<>
<Head>
<title>Codeitmall</title>
</Head>
<SearchForm />
<ProductList className={styles.productList} products={products} />
</>
)
}
(이 함수에서 리액트 훅은 사용 불가)
위 코드는 정적 생성을 할 때 next.js가 실행할 함수를 구현한 것이다.
이름을 getStaticProps
로 짓고 export를 하면 정적 생성을 할 때 next.js가 이 함수를 실행해준다.
정적 생성을 할 때 props으로 내려줄 값을 만드는 함수,
페이지 컴포넌트에 props로 내려준다.
저장하고 다시 빌드하면 상품 목록까지 렌더링 된 것을 볼 수 있다.
자주 바뀌는 데이터 같은 경우에는 정적 생성이 적절하지 않다.
정리
Next.js에서는 기본적으로 정적 생성을 해준다.
만약 정적 생성을 하면서 데이터를 가져와서 쓰고 싶으면getStaticProps
라는 함수를 만들고 export 한 다음에 리턴 값으로 props를 만들어주고 페이지 컴포넌트에서 가져와서 사용하면 된다.
정적 생성 : 모양이 고정된 페이지 X -> 빌드할 때 프리렌더링을 하는 페이지
getStaticProps 함수 안에서는 useRouter를 사용할 수 없다.
대신에 파라미터로 받는 context 객체에서 params 값을 가져올 수 있다.
그리고 getStaticPaths 함수를 사용해서 어떤 페이지들을 미리 만들어야 할 지 알려줘야 한다.
export async function getStaticPaths(){
return{
paths: [
{params:{id:'1'}},
{params:{id:'2'}},
],
fallback: false,
}
}
export async function getStaticProps(context) {
const productId = context.params['id'];
const res = await axios.get(`/products/${productId}`);
const product = res.data;
return{
props:{ product };
}
export default function Product({product}) {
...
다이나믹한 페이지를 정적 생성할 때 어떤어떤 페이지를 생성해야 하는지 정해주는 용도
params 값은 사이트 주소에서 가져오는 값이므로 문자열 형태로 작성해야 함
없는 경로에 대한 처리
false로 처리하면 404페이지로 이동한다.
fallback: true
라고하면 생성되지 않은 페이지로 접속했을 때 getStaticProps() 함수를 실행해 페이지를 만들어서 보여준다.
fallback: true
라고 지정했다면, 필요한 데이터가 존재하지 않을 수 있기 때문에 적절한 예외처리를 해야 하는데, { notFound: true } 를 리턴하면 데이터를 찾을 수 없을 때 404 페이지로 이동시킬 수 있다.
if (!product) return (
<div className = {styles.loading}>
<Spinner/>
</div>
};
이런 식으로 로딩 화면이 보이다가 서버에서 getStaticProps를 실행해서 Props를 받아오면 화면이 보인다.( 이 코드는 페이지 컴포넌트 안에 위치)
이상한 경로로 접근하는 경우 try catch문을 활용해 오류를 잡고 404페이지로 가게 한다.
export async function getStaticProps() {
const productId = context.params['id'];
try {
const res = await axios.get(`/products/${productId}`);
const product = res.data;
} catch {
return{
notFound: true;
return{
props:{
product
};
}
export async function getStaticPaths(){
const res = await axios.get(`/products/`);
const products = res.data.results;
const paths = products.map((product) => ({
params: {id: String(product.id)},
}));
return{
paths,
fallback: false,
};
}
import MovieList from '@/components/MovieList';
import SearchForm from '@/components/SearchForm';
import styles from '@/styles/Home.module.css';
import axios from '@/lib/axios';
export async function getStaticProps() {
const res = await axios.get('/movies/');
const movies = res.data.results ?? [];
return {
props: { movies },
};
}
export default function Home({ movies }) {
return (
<>
<SearchForm />
<MovieList className={styles.movieList} movies={movies} />
</>
);
}
프리렌더링을 하고 싶은데 query string 값에 따라서 다르게 렌더링 하고 싶을 때는 서버사이드 렌더링을 사용한다.
getServerSideProps
함수 사용search.js
export async function getServerSideProps(context) {
const q = context.query['q'];
const res = await axios.get(`/products/?q=${q}`);
const products = res.data.results ?? [];
return {
props: {
products,
q,
},
}
}
export default function Search({q, products}){
return (
<>
<Head>
<title>{q} 검색 결과 - Codeitmall</title>
</Head>
<SearchForm initialValue={q} />
<h2 className={styles.title}>
<span className={styles.keyword}>{q}</span> 검색 결과
</h2>
<ProductList className={styles.productList} products={products} />
</>
);
}
[id].js
사이즈 추천 부분은 많이 바뀌는 정보이다. -> 서버사이드 렌더링 한다면 해결 가능
주의! Next.js는 정적 생성과 서버사이드 렌더링을 동시에 할 수는 없다.
export async function getServerSideProps(context) {
const productId = context.params['id'];
let product;
try {
const res = await axios.get(`/products/${productId}`);
product = res.data;
} catch {
return {
notFound: true,
};
}
const res = await axios.get(`/size_reviews/?product_id=${productId}`);
const sizeReviews = res.data.results ?? [];
return {
props: {
product,
sizeReviews,
}
}
}
export default function Product({ product, sizeReviews }) {
...
필요한 데이터를 컴포넌트의 props로 받고 state에 기본 값으로 담아놓은 다음에 state를 사용해서 렌더링을 한다.
사용자가 입력폼을 작성하고 나서 데이터를 전송하면 서버에서 데이터가 변경되고 그 결과를 보내준다.
클라이언트는 그 결과를 state에 곧바로 반영해서 state 값이 바뀌면 페이지 컴포넌트가 다시 렌더링.
즉, 화면에 데이터가 곧바로 렌더링
이런식으로 페이지 컴포넌트를 state로 만들고 이걸 변경하는 식으로 활용하면 프리렌더링을 하면서도 인터렉티브한 페이지를 만들 수 있다.
export default function Product({ product, sizeReviews: initialSizeReviews }) {
const [sizeReviews, setSizeReviews] = useState(initialSizeReviews);
async function handleSubmit(e) {
e.preventDefault();
const sizeReview = {
...formValue,
productId: product.id,
};
const res = await axios.post('/size_reviews/',sizeReview);
const newSizeReview = res.data;
setSizeReviews((prevSizeReviews)=>[
newSizeReview,
...prevSizeReviews,
]);
};
async function handleInputChange(e) {
const {name, vaule} = e.target;
handleChange(name, value);
}
async function handleChange(name,value){
setFormValue({
...formValue,
[name]: value,
});
};
if (!product) return (
<div className={styles.loading}>
<Spinner />
</div>
);
return (
Next.js에서는 기본적으로 모든 페이지를 코드 스플리팅해준다.
맨처음 접속했을때는 프리렌더링 된 HTML을 전부 보내주고
적절한 타이밍에 코드 스플리팅 된 자바스크립트 코드를 받아서 리액트에서 실행하고
Link를 클릭하면 필요한 데이터를 JSON으로 받아와서 화면을 업데이트한다.
App Router의 가장 큰 차이는 /pages 폴더가 아닌 /app 폴더에 페이지 컴포넌트들을 추가한다는 것이다.
그리고 이 페이지 컴포넌트들은 기본적으로 리액트 서버 컴포넌트(React Server Component)이다.(서버에서만 렌더링되는 컴포넌트)
리액트 서버 컴포넌트는 서버에서만 렌더링하는 컴포넌트
리액트 서버 컴포넌트와 리액트 클라이언트 컴포넌트의 문법에서 가장 큰 차이는 데이터를 가져오는 방식이다.
Next.js에서 프리렌더링을 한다면 데이터를 Props로 내려받았는데, 리액트 서버 컴포넌트를 사용하면 컴포넌트를 async/await 함수로 만들 수 있고, 함수 최상위(top-level)에서 await 로 데이터를 가져올 수 있다.
async function getData() {
const res = await fetch('https://api.example.com/...');
return res.json();
}
export default async function Page() {
const data = await getData();
return <main> ... </main>;
}
참고 자료
React Labs: What We've Been Working On – March 2023 – React
Server Components - Next.js
Introducing Zero-Bundle-Size React Server Components