React로 커뮤니티 사이트 프로젝트를 진행중에 SPA를 사용할 경우 seo문제가 있다는 것을 알게 되었다. Next.js 프레임워크를 사용하면 react를 node환경에서 사용할 수 있기 때문에 페이지에 따라 pre-rendering, csr을 쉽게 선택적으로 적용할 수 있고 코드 스플리팅과 페이지 최적화가 자동으로 이루어진다. 또한 typescript, sass, autoprefix, babel 적용도 간단하게 할 수 있어 도입하게 되었다.
npx create-next-app .
npm i @types/node @types/react @typescript
npm i sass
npm run dev
개발환경 실행.
cra보다 가볍고 불필요한 잡다한 파일이 적다.
typescript, sass는 설치와 동시에 적용된다.
기본적으로 es6문법이 사용가능하고, autoprefix가 적용되어 있다.
pages 폴더에 만들어진 파일은 라우트 경로가 된다.
import Link from 'next/link';
// 컴포넌트
<Link href="/project/route"><a>content</a></Link>;
Link의 route경로는 /pages/project/route.js파일 컴포넌트와 연결되어 있고
localhost:3000/route로 이동하면 컴포넌트 내용을 볼 수있다.
*라우트가 아닌 외부링크를 사용시에는 Link 사용 x a태그에 주소를 입력.
className같은 속성을 부여할 일이 있을 경우 Link가 아닌 a 태그에 부여할 것.
metadata 처리
import Head from 'next/head';
<Head>
<title>Main Page</title>
</Head>
서버에서 pre-rendering을 한 후에 브라우저로 파일을 넘겨주기 때문에
metadata가 적용된 상태로 브라우저가 html파일을 받아볼 수 있게된다.
dynamic Routes
동적라우팅이 필요한 컴포넌트의 파일명은 [파일명].js으로 정의
// pages/index.js
<Link href="dynamic/ok"><a>dynamic pages</a></Link>
// pages/dynamic/[id].js
import { useRouter } from 'next/router';
const Post = () => {
const router = useRouter();
const { id } = router.query;
console.log(query) // {id : 'ok'}
return <p>Post: {pid}</p>;
};
export default Post;
[...id]로 정의하게 되면 {id: [1,2,3]} 은 post/1/2/3 링크와 매치가능.
/의 개수만큼 폴더를 만들지 않아도 Link에 /를 여러번 사용가능.
css
css를 모듈 사용하기 위해서는 파일 이름이 .module.css로 끝나야 한다.
모듈이 아닌 전 페이지에 영향을 미치는 css파일을 사용하기 위해서는
pages/_app.js에서 모듈이 아닌 css파일을 불러온다.
// index.module.css
.title { text-align: center; transition: 100ms ease-in background; }
.title:hover { background: #ccc; }
//index.js
import styles from './index.module.css';
export default function Home() {
return(
<>
<h1 className={styles.title}></h1>
</>
)
};
Static Generation, Server-side Rendering 두 형태로 구분된다.
next.js는 컴포넌트에서 리턴한 node들을 정적 html파일로 build-time에 미리 렌더링한다.
즉 브라우저가 컴포넌트를 js로 렌더링하는 것이 아니라 서버에서 렌더링하는 것이다.
Static Generation(ssg)
작성한 page들의 모든 컴포넌트를 정적 HTML 파일로 build-time에 렌더링하여 준비시킨 뒤 각각의 페이지들이 요청할 때에 페이지에 해당하는 html파일들을 내어준다.
즉 컴포넌트 모든 html파일을 미리 렌더링해 놓고 대기시키는 것이다.
Server-side Rendering(ssr)
페이지가 요청하는 순간에 그 페이지에 알맞은 컴포넌트를 정적 html파일로 렌더링하여 브라우저 내어준다.
웬만한 경우에는 static Generation 방식을 사용하는 것이 좋다. 하지만 페이지가 요청할때마다 동작이나 내용이 달라져야 하거나 빈번하게 페이지 내용이 변화하여 최신의 내용을 전달해야할 경우 ssr 또는 ssg이후 csr을 적용해야한다.
(static Generation은 build-time에 html을 준비시켜놓기 때문에 build-time 이후의 변화요청에는 반응하지 않음. 개발모드에서는 항상 반응하니 주의.)
이러한 경우에 seo가 필요하다면 ssr을 선택하고 보통은 csr로 동적변화를 처리한다.(장바구니에서 수량하나 증가시키는데 ssr을 적용시킬 필요는 없을 것이다.)
next.js는 기본적으로 static generation이 적용되어 있으며 외부데이터(API, DB)등을 포함하여 렌더링해야할 때 ssg ssr을 선택적으로 적용할 수 있다.
getStaticProps 함수
API, DB 등 외부 데이터를 포함하고 있을 경우 렌더링 하는 방법.
async function으로 컴포넌트 내에 외부데이터를 포함하여 pre-render하기 위해 사용
export async function getStaticProps() {
const allPostsData = getSortedPostsData(); // 외부데이터
// return props: {data} 형식을 지켜야함.
return {
props: {
allPostsData,
},
};
}
먼저 getStaticProps 함수 내에 외부 데이터를 props객체로 반환한다.
export default function Home({ allPostsData }) {
return (
allPostsData.map(e => ....)
);
}
컴포넌트에서 prop으로 받아와 사용한다.
즉 외부데이터를 pre-render하기전에 getStaticProps함수를 한번 거치는 것이다.
초기 렌더링 때 한번 실행된 후 실행안됨.
외부데이터를 이용하여 data를 build-time에 fetching하면서
data에 따라 다른페이지를 보여줄 동적 라우팅이 필요한 경우
getStaticPaths, getStaticProps를 함께 사용.
export async function getStaticPaths() {
return {
paths: [
{ params: { id: "1" } },
{ params: { id: "2" } },
],
fallback: false,
};
}
getStaticPaths 함수에서 return하는 객체는 꼭 위와 같은 형식과 이름이 지켜져야한다. paths, params
그런데 id의 경우에는 동적라우트 컴포넌트 파일명을 따른다
여기서는 [id].js이기 때문에 id: "1"을 해준 것.
[hi].js라면 hi: 1로 설정. Link href 주소와 매칭이 되어야 한다.
만약 getStaticPaths함수의 params가 수천개가 넘어갈 경우
빌드타임에 수천개의 페이지가 만들어지는 문제점이 있다.
이를 해결하는 것이 fallback : true
fallback : true, false, blocking이 있음.
export async function getStaticProps(context) {
console.log(context.params.id)
const postData = context를 이용하여 데이터를 가공.
return {
props: {
postData,
},
};
}
getStaticProps의 context에는 getStaticPaths에서 리턴한 값이 저장되어있다.
export default function Post({postData}) {
return ()
}
원하는 컴포넌트에서 가공된 데이터를 활용한다.
빌드타임 이후 유저의 요청(request time)에 즉각 반응하여 SEO를 적용한 새로운 외부 데이터를 제공해야 하는 ssr의 경우
getServerSideProps 함수를 사용
export async function getServerSideProps(context) {
return {
props: {
// props for your component
}
}
}
page 요청때마다 항상 실행