Next.js 는 React로 SSR(Server Side Rendering) 을 보다 쉽게 구현하기 위한 프레임워크 이다. 따라서 그 전에 SSR과 그와 관련한 CSR, SSG에 대해서 먼저 알아보자.
MPA
다수의 페이지로 구성되어 요청마다 정해진 페이지를 반환
SPA
단일 페이지로 구성되어 해당 페이지 자체에서 요청에 따라 화면을 재구성
MPA가 SSR방식이다. MPA 형태에서는 이미 하드코딩 된 정적 페이지들을 개별적으로 서버가 가지고 있다. 따라서 요청마다 서버는 해당 요청에 상응하는 정적페이지를 하나 찾아서 반환을 해주게 된다. 이는 결국 렌더링이 서버 사이드에서 완료되어 최종 문서를 반환하고 있는 형태이다.
SSR은 HTML파일을 렌더링을 마친 상태로 응답하기 때문에 로딩시간이 상대적으로 짧다.
장점
단점
SPA가 CSR방식이다. SPA 형태에서는 최초 요청 시에 서버로부터 HTML 문서를 전달받는다. 해당 문서는 비어있는 HTML 문서나 다름이 없다. 최초로 받아온 HTML문서를 가지고 렌더링에 필요한 기타 다른 파일(JS같은거)을 로드하여 클라이언트단에서 렌더링을 실시해 화면을 구성한다.
하나의 페이지에 여러 페이지를 보여 주는것은 자바스크립트를 이용해서 페이지의 일부분이나 전체를 바꾸는것이다. jsx와 js같은 파일들은 Webpack과 같은 번들링 도구를 거쳐 하나의 거대한 js파일로 번들링한다. SPA에서는 이처럼 번들링 된 js를 전달받아 Single Page Application을 구축하게 된다.
즉 CSR 방식 에서는 번들링이 완료된 js 파일을 모두 로드하기 전에는 첫 페이지를 로드 할 수가 없다. (처음엔 빈 html파일이 로드 돼있다.)
장점
단점
Static-Rendering 라고도 한다. 해당 방식은 클라이언트에서 필요한 페이지들을 서버에서 사전에 미리 준비해 뒀다가, 요청을 받으면 이미 완성된 파일을 단순히 반환하여 브라우저에서 뷰를 보여지게 하는 방식이다. SSR과 비슷해 보이지만 다른점은 HTML 파일의 생성 시점이 빌드타임 이라는 것.
SSR은 엄밀히 말하면 SSG에 속한다.(둬허란나 줘헌나 헷갈리네...)
장점
단점
SPA는 기본적으로 CSR 방식을 사용하여 동작한다. 하지만, CSR 방식이 가진 단점을 극복하려고 SSR개념을 !부분 적용!하면 좋은데, 그것을 쉽게 하기 위해 NEXT를 사용하는 것이다.
즉, NEXT는 React와 같은 SPA(CSR)에 SSR을 쉽게 적용하기 위한 프레임워크 및 라이브러리이다.
외에도 Code Splitting, Image Optimazation, Pre-fetching과 같은 기능도 함께 제공하고있다.
첫 페이지 로딩을 SSR방식으로 적용해 렌더링 하고, 나머지는 CSR을 쓰는 방식으로 하면 각자의 장점을 사용 할 수 있어서 좋다. SSR로 인해 초기 렌더링 속도가 빠르고 SEO에 최적화 되어있으며, 이후 CSR을 이용하여 페이지간 이동이 빠르고 새로고침 현상이 없게 되기 때문이다.
NEXT는 브라우저에 렌더링 할 때 기본적으로 pre-redering을 한다고 소개한다.(공식문서)
React에서는 (어떠한 라이브러리나 프레임워크를 사용하지않은) CSR 방식이 사용된다. 하지만 React와 NEXT.를 함께 사용하게 되면 pre-rendering을 하여 빌드 타임때 해당하는 페이지 별로 html문서를 미리 생성해 가지고 있다가 서버로 요청이 들어올 때 알맞은 페이지를 반환해준다.
NEXT에서 Pre-rendering을 하기 위해 두가지 방식을 사용하는데 바로바로 SSG와 SSR이다!
두 방식은 모두 pre-rendering을 하지만 언제, 어떻게 제공하는지의 차이이다.
NEXT에서는 다음과 같은 경우에 SSG 사용을 권장한다.
유저의 요청 때마다 그에 상응하는 html 문서를 생성하여 반환한다.
NEXT에서는 다음과 같은 경우에 SSR 사용을 권장한다.
그렇다면 NEXT를 사용하면 CSR을 사용하지 않을까? → 아님!
위에서 말한 SSG 방식이 CSR을 대체하는 개념이다.
기본적으로 SPA는 CSR 방식이 기본이다. (NEXT에서도 링크 걸면 새로고침 없이 이동함 → NEXT도 react를 사용하니까 SPA!)
NEXT를 사용하면 CSR+(SSG or SSR) 을 사용할 수 있다.
Next 공식문서에서는 만약 데이터의 변동이 매우 빈번하게 일어난다면 굳이 pre-rendering을 취하지 말고 기존 react에서 처럼 data-fetching을 통해 클라이언트 사이드에서 렌더링 할 것을 권고한다.
예시
공식 문서에서는 예를 들어 유저 대시보드 같은 웹을 CSR 방식으로 하면 좋다고 한다.
1. 개인적인 영역으로 SEO를 적용해야할 필요가 거의 없어서.
2. 유저의 수정을 통해 즉각적으로 화면이 변동하기 때문에
yarn create next-app
//or
npx create-next-app
npx create-next-app --typescript
//or
yarn create next-app --typescript
npm install next react react-dom
//or
yarn add next react react-dom
"scripts": {
"dev": "next dev", //development mode에서 next 시작
"build": "next build", //어플리케이션 build
"start": "next start", //next production server 시작
"lint": "next lint" // eslint 에서의 Next setting
}
위와 같은 설치 후에는 yarn dev
명령어를 통해서 (기존 react에서 yarn start와 같음) 어플리케이션을 locallhost:3000
에서 실행 시킬 수 있다.
NEXT에서 page는 pages폴더 내에서 .js
.jsx
.ts
.tsx
파일에서 내보내지는 리액트 컴포넌트 이다.
각 page는 route와 연관되어있는데 pages 폴더 내에 파일 이름을 기반으로 쉬운 라우팅을 할 수 있다! 이는 next가 제공하는 automatic routing 기능 이다.
기본적으로 메인페이지를 이루는 라우팅인 /
는 pages 내부의 index.tsx or index.jsx 파일이 내보내는 컴포넌트가 된다.
만약에 **pages 폴더 내에 test.tsx 라는 파일을 만들거나, test폴더를 만들어 내부에 index.tsx 파일을 생성한다면** 해당 웹 페이지의 라우팅 주소는
페이지 주소/test
만약 pages 폴더 내에 auth 폴더를 만들고 내부에 login.tsx , join.tsx 파일을 만들면 각 페이지 라우팅 주소는
페이지 주소/auth/login
페이지 주소/auth/join
이와 같이 단순히 폴더를 생성하고 파일을 만들어 주는 것 만으로도 라우팅 작업을 따로 하지 않아도 된다!
위와 마찬가지로 pages 폴더 내에 폴더와 파일을 만들어 주면 된다. 단, 파일 이름을 [id].tsx 와 같이 만들어 줄것.
그러면 라우팅 주소는
/test/[id]
와 같이 될 것이고 실제로 /test/1, /test/2 와 같이 동적 라우팅을 할 수 있다.
📂 pages
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── 📂 auth
│ │ ├── join.tsx
│ │ └── login.tsx
│ ├── index.tsx
│ ├── 📂 map
│ │ ├── [areaId].tsx
│ │ ├── index.tsx
│ │ └── list.tsx
│ ├── mypage.tsx
│ ├── 📂 review
│ │ ├── 📂 detail
│ │ │ └── [reviewId].tsx
│ │ ├── 📂 my
│ │ │ ├── emptyScrap.tsx
│ │ │ ├── emptyWrite.tsx
│ │ │ ├── scrap.tsx
│ │ │ └── write.tsx
│ │ └── write.tsx
│ └── 📂 shop
│ ├── collect.tsx
│ ├── collectEmpty.tsx
│ ├── 📂 detail
│ │ └── [id].tsx
│ └── 📂 theme
│ └── [type].tsx
기본 메인이 되는 홈페이지
가장 먼저 실행되며, 레이아웃을 잡거나 각 페이지 별로 공통 로직을 처리할 때 사용할 수 있다.(global css 사용, Header/Footer/Navbar 등 삽입)
_app.tsx 이후에 실행된다. 공통적으로 사용하는 메타 태그 등을 삽입할 수 있는 파일이다.
해당 파일에서는 이벤트 핸들러를 사용할 수 없다.
정적 자원 중 CSS 파일을 별도로 보관하는 폴더.
global css를 보관하게 되며 이러한 global css는 오직 _app.tsx 파일 에서만! import 하여 사용하면 된다. 그러면 모든 컴포넌트 파일에서 사용 가능하다.
정적 자원을 주로 보관하는 root 디렉토리 이다.
주로 assets 파일(images, fonts 등등)들을 넣어둠.
NEX는 Pre-Rendering을 통해서 모든 페이지를 미리 렌더링 하고 성능 향상과 SEO 최적화를 진행한다.
이 과정을 어떻게 구현하는지 알아보쟈!
getStaticProps를 사용하게 되면, NEXT는 getStaticProps로 부터 반환된 props를 이용하여 getStaticProps가 사용된 페이지를 pre-render 한다.
getStaticProps는 빌드 시에 딱 한 번만 호출 되고 바로 static file로 빌드 되어 정적 파일이 된다. 따라서 이후 수정이 불가능하다.
딱 한번 호출 후에 매번 data fetch를 하지 않으니 아주아주 빠르다.
getStaticProps가 기본적으로 받는 context 매개 변수
반환값
사용 예시 코드 (공식 문서)
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>))}
</ul>)
}
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
}
}
export default Blog
동적 라우팅 페이지에서 getStaticProps로 ****pre-render하여 static 페이지를 생성할 때 사용한다. (즉, 동적 라우트 페이지 인데 getStaticProps를 사용하고 싶을 때)
동적 라우팅을 사용할 때, 어떤 페이지를 미리 static으로 빌드할 지 정하여 경로를 제공해 주는 것이다.
반환 값
사용 예시 코드
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>))}
</ul>)
}
export const getStaticPaths = async () => {
const res = await fetch('https://.../posts')
const posts = await res.json()
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// { fallback: false } 는 다른 routes들은 404임을 의미
// true이면 만들어지지 않은 것도 추후 요청이 들어오면 만들어 줄 거라는 뜻
return { paths, fallback: false }
}
export async function getStaticProps({params}) {
const res = await fetch(`https://.../posts/${params.id}`)
const posts = await res.json()
return {
props: {
posts,
},
}
}
export default Blog
getServerSideProps는 빌드와 상관 없이 매 페이지 요청마다 Pre-rendering 하여 데이터를 서버로 부터 가져온다. (매 페이지 요청마다 새로 렌더링 하여 반환하는 것)
매 요청마다 호출 되어 성능은 getStaticProps보다 떨어지지만 (소담 눈감아..) 내용이 언제든 동적으로 수정 가능하고 보여진다.
getServerSideProps는 서버사이드에서만 실행되고, 절대로 브라우저에서 실행되지 않는다. 또한, 요청 시점에 실행되고 그 결과의 값을 props로 넘겨준 뒤 렌더링을 하게된다.
페이지를 렌더링 하기 전에 반드시 fetch 해야할 데이터가 있을 때.
데이터가 새로고침 마다 동적으로 변경될 경우
렌더링 하기전에 반드시 fetch 해야하는 경우가 아니라면 CSR이나 SSG방식을 사용하는것이 좋다구 합니다. (소담 눈감아 22..)
getServerSideProps가 기본적으로 받는 context 매개 변수
반환값
사용 예시 코드
const Detail = ({ item }) => {
return (
<div className="Detail">
<h1>{item.title}</h1>
<p>{item.body}</p>
<p>{item.id}번째 게시글</p>
</div>
);
};
export default Detail;
export const getServerSideProps = async (context) => {
const id = context.params.id;
const res = await axios.get(
`https://.../posts/${id}`);
const data = res.data;
return {
props: {
item: data,
},
};
};
현재는 getInitialProps 대신에 위의 세가지 방식을 사용하는것을 권장한다.
NEXT는 기본적으로 자동 정적 최적화를 하는데 자동 정적 최적화란, getInitialProps가 없으면, 페이지를 정적 HTML 으로 사전렌더링 해서 정적 최적화를하는 것이다.
그러나 전역적으로 getInitialProps를 사용하게 되면, 이러한 최적화 과정이 일어나지 않는다. 때문에 현재는 분리해서 위의 세가지 방식을 사용하는 것이다.
위의 세가지 방식은 전역적인 데이터 패치 기능을 지원하지 않는다. 따라서 전역적으로 SSR 패칭을 해야만 하는 경우라면 getInitialProps를 써야만 한다! 페이지 별로 데이터 패칭은 위의 세가지 방식을 사용한다
getServerSideProps가 기본적으로 받는 context 매개 변수
사용 예시 코드
_app.tsx 파일 내부
function MyApp({ Component, pageProps }) {
return (
<>
<Head>
<title>Next!</title>
</Head>
<AppLayout>
<Component {...pageProps} />
</AppLayout>
</>
);
}
MyApp.getInitialProps = async (context) => {
const { ctx, Component } = context;
let pageProps = {};
if (Component.getInitialProps) {
// Component의 context로 ctx를 넣어주자
pageProps = await Component.getInitialProps(ctx);
}
// return한 값은 해당 컴포넌트의 props로 들어가게 됩니다.
return { pageProps };
};
NEXT에서도 CSR방식을 사용하여 데이터 패칭을 할 수 있다. 우리가 일반적으로 리액트에서 맨날 하던 그것이다.
const About = () => {
const [list, setList] = useState([]);
useEffect(() => {
const getList = async () => {
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts`);
const data = res.data;
setList(data);
};
getList();
}, []);
return (
<div className="About">
<h1>여기는 About 페이지요!</h1>
{list.length &&
list.slice(0, 10).map((item) => <li key={item.id}>{item.title}</li>)}
</div>
);
};
router 내부에 라우트 정보 객체를 가지고 있으며, 다양한 메소드(etc: .push / .back / .replace)를 이용할 수 있다.
import { useRouter } from "next/router";
export default function App() {
const router = useRouter();
return (
<div>
<h2>Link to 'tomato' Page</h2>
<button onClick={() => router.push("/tomato")}>토마토로 가기</button>
</div>
);
}
그러나 seo에 영향을 미치는 크롤러는 router.push() 가 다른 페이지로 이동하는 링크라고 인식하지 않는다.
따라서, Link 컴포넌트를 활용하는게 좋다.
NEXT에 내장 된 클라이언트 사이드 라우팅 기능
import Link from 'next/link'
function Home() {
return (
<ul>
<li>
<Link href="/">
<a>Home</a>
</Link>
</li>
<li>
<Link href="/about">
<a>About Us</a>
</Link>
</li>
<li>
<Link href="/blog/hello-world">
<a>Blog Post</a>
</Link>
</li>
</ul>
)
}
export default Home
동적 라우팅
import Link from 'next/link'
function Posts({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${encodeURIComponent(post.slug)}`}>
<a>{post.title}</a>
</Link>
</li>
))}
</ul>
)
}
export default Posts
custom된 a태그를 래핑하는 link 태그 or 함수로 반환된 Component를 래핑하는 link 태그 → passHref를 꼭 써줘야됨 (소담에서는 Button 태그를 래핑할 때랑 여러 태그를 한꺼번에 래핑하는 경우에도 사용해줬는데.. 정확하게 모르겠다 아마 Lint에 걸렸던듯)
import Link from 'next/link'
import styled from 'styled-components'
const RedLink = styled.a`
color: red;
`
function NavLink({ href, name }) {
return (
<Link href={href} passHref>
<RedLink>{name}</RedLink>
</Link>
)
}
export default NavLink
이미지를 자동으로 최적화 해줌
built-in 성능을 최적화 하기 위해 를 확장한 것.
import Image from 'next/image';
function ShopCard(props: ShopCardProps) {
const { cardData } = props;
const { image, shopName, category, shopId } = cardData;
const joinCategory = () => {
if (typeof category === 'string') return category;
return category.join(', ');
};
return (
<Link href={`/shop/detail/${shopId}`} passHref>
<StyledRoot>
<Image
src={image[0]}
width={282}
height={208}
alt="thumbnail"
placeholder="blur"
blurDataURL={image[0]}
/>
<StyledTitle>
<h3>{shopName}</h3>
<p>{joinCategory()}</p>
</StyledTitle>
</StyledRoot>
</Link>
);
}
페이지에 title, meta 태그를 넣어주기 위한 컴포넌트 → seo 사용하기 위해서 meta 태그가 필요함
import Head from 'next/head'
function IndexPage() {
return (
<div>
<Head>
<title>My page title</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<p>Hello world!</p>
</div>
)
}
export default IndexPage
참고자료
Getting Started | Next.js
CSR, SSR, SSG 조화를 이루다.
[FE] CSR(Client-Side-Rendering) vs SSR(Server-Side-Rendering) (feat. React를 중점으로)
SSR과 CSR의 차이
next.js 기본 개념 알아보기
Next.js 프로젝트 디렉토리 구조
Next.js 100% 활용하기 (feat. getInitialProps, getStaticPath, getStaticProps, getServerSideProps, storybook)