https://www.udemy.com/course/react-next-master/
nextjs는 Vercel
이 개발한 오픈소스 자바스크립트 framework로 react에 추가적인 기능들을 제공한다. 따라서, nextjs를 react의 확장판으로 생각하면 된다.
굉장히 다양한 기능들을 제공하는데 대표적으로는 다음과 같다.
1. directory 기반의 page routing
2. image, font, script 최적화
3. server side rendering(SSR)
가장 중요한 것이 server side rendering이다. react만쓰면 client side rendering(CSR) 방식으로만 동작하는데, nextjs를 쓰면 CSR과 SSR을 같이 쓸 수 있다.
먼저 CSR방식으로 동작하는 react app을 보도록 하자.
------------------ 1.html file --> --------- 2. display rendering --> ------
|React Web server| |Browser| |화면|
------------------ 3.bundled js file--> --------- 4. content rendering --> ------
맨 처음 하나의 html 파일 (index.html)을 browser에 전달하고, 이를 js code기반으로 화면에 그려준다. 다음으로 사용자의 interaction으로 변화하는 component들에 대해서 bundled js file을 이용해 component를 렌더링해주는 것이다.
따라서, 맨 처음의 html 파일 하나 만으로 여러 개의 page를 구현한 것처럼 보이게 만든 것일 뿐이다. 장점으로는 page간의 이동(실제 html파일을 바꾸는 것이 아닌, component를 렌더링하는 것)이 매우 빠르지만, 단점으로는 첫 페이지의 로딩이 매우 느리다. 왜냐하면 bundled js file을 넘긴다는 것은, 아직 렌더링에 필요하지 않은 component 구현 사항들도 모두 전달한다는 것이기 때문이다.
가령, Home
화면을 하나 그리는데, 결제 page에 필요한 component의 js code가 필요하진 않을 것이다. 그러나, CSR의 경우 모든 js code를 처음에 넘겨주어야 하기 때문에 초기 로딩 속도가 느릴 수 밖에 없어진다.
이를 해결해주는 것이 SSR이다.
------------------ 1.완성된 html file--> --------- 2. 완성된 화면 렌더링 --> ------
|nextjs Web server| |Browser| |화면|
------------------ 3.bundled js file--> --------- 4. 인터렉션, 페이지 이동 등 --> ------
nextjs는 빈 html file이 아니라, 완성된 html file을 page마다 바로바로 Browser에 전달한다. 즉, CSR과 달리 초기에 완성된 page를 보내준다는 것이다. 이후 사용자의 interaction이 발생하면 bundled js file에 있는 js component들을 이용하는데, 만약 Home
화면을 그리고 있다면 결제 page의 component는 필요없기 때문에 해당 js code는 split하여 전달하지 않는다.
즉, 결론적으로 page마다 이미 렌더링된 html파일을 전달하여, react의 초기 page의 느린 렌더링 속도를 보완하고, page내의 렌더링은 react가 가진 CSR을 이용하여 효율적으로 화면을 그려준다는 것이다.
여기에 nextjs는 더불어 검색 엔진 최적화(SEO)를 위한 기능도 제공한다. 검색 potal site에서는 robot을 통해서 다양한 page들의 정보를 수집하고 db에 저장한다. 이후 사용자가 검색한 키워드와 연관된 정보를 결과로 우리의 page들을 노출시켜주는데, 문제는 대다수의 검색 사이트가 html기반으로 page정보를 수집한다는 것이다. react의 경우 초기 html파일은 빈 껍데기이므로 검색 사이트의 엔진이 html 기반으로 page 정보를 수집할 때, 제대로 된 정보를 수집하지 못하게 되는 것이다.
그런데, nextjs는 초기에 html page를 이미 렌더링되어 있기 때문에 검색 사이트 robot이 html page를 쉽게 파싱하여 정보를 저장할 수 있다.
npx
를 사용하여 설치하면 된다.
npx create-next-app@latest .
지금은 study용이기 때문에 eslint
말고는 그냥 모두 no라고 하자. package.json
을 통해 어떤 library가 설치되었는지 확인해보도록 하자.
{
"name": "section12",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"react": "^18",
"react-dom": "^18",
"next": "13.4.12"
},
"devDependencies": {
"eslint": "^8",
"eslint-config-next": "13.4.12"
}
}
react
와 nextjs
가 모두 설치된 것을 볼 수 있다. next.js
를 실행하는 방법으로 npm run dev
를 실행보도록 하자.
npm run dev
를 실행하면 localhost:3000
으로 nextjs app이 실행되어 열린 것을 볼 수 있다.
이제 project 구조를 보도록 하자.
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
export default nextConfig;
다양한 nextjs 기능들을 추가할 수 있다.
jsconfig.json
은 js를 어떻게 compile할 것인지 option을 정하는 것이다.
{
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}
추후에 변경하면서 알아보겠지만 paths
로 alias를 설정하여 import를 편하게 부를 수 있다.
다음으로 pages
directory를 보면 실제 js파일들이 있는데, pages
directory안에 있는 js
file들은 각각의 page 역할을 하는 파일들로 생각하면 된다. 가령 pages
의 index.js
는 index.html에 쓰이는 file로 생각하면 된다.
styles
는 css 모듈들이 있는 곳이다. 이를 통해 nextjs는 css를 모듈로서 사용한다는 것을 알 수 있다.
nextjs는 page router
와 app router
를 모두 제공하는데, app router
는 버그들이 많고 입문자들이 배우기 어렵다. 따라서, 지금까지 계속 사용되던 방식인 page router
를 배우는 것이 좋다.
page router
는 directory기반의 라우팅을 제공한다. 다음의 directory들이 있다고 하자.
pages
- index.js
- about.js
post
- index.js
- [id].js
news
- index.js
/
로 요청이 오면 index.js
이 렌더링되고, /about
이면 about.js
가 렌더링된다. 또한, /post
로 요청이 오면 post.js
가 없기 때문에 post
의 index.js
가 렌더링된다. 만약, 동적 경로(URL parameter)가 필요한 경우 [id].js
를 이용하면 되는데, /post/1
이 요청이 오면 [id].js
가 렌더링된다.
만약 /none
과 같이 만들지 않은 url을 요청하면 404 not found가 나온다.
pages
directory를 보면 _app.js
, _document.js
와 같은 component를 볼 수 있는데, 이들은 모든 page component들에 적용되는 공통 요소들로 보면 된다.
import "@/styles/globals.css";
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />;
}
App
은 마치 react의 App
component와도 같은데 pages
directory에 있는 page들이 Component
props로 전달되어, child로 렌더링한다. 즉, 모든 page component들의 root역활을 한다.
_document.js
는 react
의 index.js
와 비슷한 역할로 생각하면 된다. 즉, 가장 기본 template가 되는 영역이라고 생각하면 된다.
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
그래서 Html
lang을 en
에서 kr
로 바꾸면 모든 page들의 lang설정이 kr
이 된다.
nextjs에서 page router로 URL parameter를 받는 방법은 매우 간단하다. 만약 /search/KOR
, /search/ITA
와 같이 뒷부분을 URL paramter로 받고 싶다면, 다음과 같이 pages
directory에 js파일을 만들면 된다.
pages/country/[code].js
[]
안에 URL parameter의 이름을 적어주면 된다. 이 URL parameter를 받아내는 것은 nextjs에서 제공하는 useRouter
를 사용하면 되는데, 반환된 router는 URL과 관련된 대부분의 정보들을 담고 있다.
pages/country/[code].js
import { useRouter } from "next/router"
export default function Country() {
const router = useRouter()
const { code } = router.query
return (
<div>Country {code}</div>
)
}
code
에 URL parameter로 전달한 값이 전달된다. 만약 [code].js
가 아니라 [id].js
였다면 router.query
안에는 id
property로 값이 저장되어있을 것이다.
그런데 만약 country/KOR/123
, country/KOR/415
, country/ITA/1254
와 같이 두 URL parameter가 연달아 나온다면 어떻게해야할까?? 이때에는 catch all표현으로 [...code].js
를 사용하면 된다. 그러면 code
안에 모든 URL paramter들의 정보들이 담긴다. 가령, country/KOR/123
이면 code는 KOR123
이고, country/ITA/1254
이면 code
는 ITA1254
이다.
또한 URL parameter값이 optional한 경우에는 [[param]]
을 써주면 된다. 가령 country/KOR
이 아니라 /country
에도 대응하도록 만들고 싶다면 [[...code]].js
로 만들면 된다.
nextjs에서 page를 이동할 때는 Link
를 사용하면 된다. 이 Link
는 CSR
방식으로 실제로의 page이동없이 page component를 변경해주는 것이다.
import Link from "next/link";
export default function Home() {
return (
<div>
<Link href={"/search"}>Search Page 이동</Link>
</div>
);
}
한 가지 조심해야할 것은 react-router
에서의 Link
는 to
에 url을 써주면 되었지만, 여기서는 href
에 써주어야 한다.
만약 URL parameter를 쓰고 싶다면 다음과 같이 쓸 수 있다.
import Link from "next/link";
export default function Home() {
const code = "KOR"
return (
<div>
<Link href={`/search/${code}`}>Search Page 이동</Link>
</div>
);
}
그러나 pages
directory에 있는 file명 기준으로 쓰고 싶을 수도 있다. 이때에는 URL object이용하면 된다.
import Link from "next/link";
export default function Home() {
const code ="KOR"
return (
<div>
<div>
<Link href={"/search"}>Search Page 이동</Link>
</div>
<div>
<Link href={{pathname: "/country/[code]", query: {code: code}}}>Search Page 이동</Link>
</div>
</div>
);
}
url object는 두 개의 property로 이루어져 있는데, 하나는 pathname
하나는 query
이다. pathname
에는 url이 아니라 pages
directory에서 어떤 component를 사용할 것인지를 지정하고, query
는 url parameter로 어떤 것을 보낼 지 결정하는 것이다.
다른 방법으로 useRouter
의 router
객체를 사용하는 방법이 있다. 위에서 언급했듯이 useRouter
로 반환되는 router
는 nextjs routing에 관한 모든 정보와 방법들을 다 가지고 있다. 따라서, 특정 page로 이동하는 것도 가능한데 router.push
를 사용하면 된다.
import Link from "next/link";
import { useRouter } from "next/router";
export default function Home() {
const code ="KOR"
const router = useRouter()
const onClickButton = () => {
router.push('/search')
}
return (
<div>
<div>
<button onClick={onClickButton}>Search page로 이동</button>
</div>
<div>
<Link href={"/search"}>Search Page 이동</Link>
</div>
<div>
<Link href={{pathname: "/country/[code]", query: {code: code}}}>Search Page 이동</Link>
</div>
</div>
);
}
이제 버튼이 눌리면 router.push('/search')
가 실행되며 /search
page에 해당하는 page component가 렌더링된다. 만약, url object를 쓰고 싶다면 다음과 같이 수정이 가능하다.
const onClickButton = () => {
router.push({pathname: "/country/[code]", query: {code: code}})
}
router
에서는 push
외에도 뒤로가기를 방지하여 page를 이동하는 replace
와 뒤로가기를 유발하는 back
, 새로고침을 유발하는 reload
등이 있다.
전체 page의 layout을 설정하고 싶으면 _app.js
component를 이용하면 된다. _app.js
에서의 App
component는 모든 component의 부모 역할을 한다.
import "@/styles/globals.css";
import Layout from "@/components/Layout";
export default function App({ Component, pageProps }) {
return (
<div>
<Layout>
<Component {...pageProps} />;
</Layout>
</div>
)
}
다음과 같이 nextjs에서의 모든 page component들은 Component
로 전달되기 때문에 Component
의 부모에 우리가 원하는 layout을 가진 Layout
component를 설정해주면 된다.
그런데 만약, 특정 page에만 component를 설정하고 싶을 때는 어떻게 해야할까?? 가령 /pages/country/[code].js
와 /pages/search/index.js
에만 SubLayout
component가 생기도록 만들고 싶을 때와 같은 경우이다.
이 경우 먼저 대상이 되는 component에게 Layou
이라는 property로 layout이 되는 component를 적용시켜주면 된다.
import SubLayout from "@/components/SubLayout"
export default function Search() {
return <div>Search Page</div>
}
Search.Layout = SubLayout
함수 object에 Layout
이라는 property로 SubLayout
을 설정해주도록 한다.
참고로 SubLayout
component는 다음과 같다.
import style from "./SubLayout.module.css"
export default function SubLayout({ children }) {
return (
<div className="SubLayout">
<div>{children}</div>
<footer className={style.footer}>@winterlood</footer>
</div>
)
}
이 다음 _app.js
로 넘어가도록 하자.
import "@/styles/globals.css";
import Layout from "@/components/Layout";
export default function App({ Component, pageProps }) {
return (
<div>
<Layout>
<Component.Layout>
<Component {...pageProps} />;
</Component.Layout>
</Layout>
</div>
)
}
이제 App
component의 Component
props에는 Layout
이라는 property안에 SubLayout
component가 있을 것이다. 이를 사용하기 위해서 Component.Layout
안에 Component
를 child로 넣어주면 된다.
그러나, 이렇게 사용할 경우 Component
에 Layout
property를 넣어주지 않은 객체에 대해서는 error가 발생한다. 즉, /
경로로 들어가 확인해보면 react error가 발생해있을 것이다. 이는 Component
에 Layout
property가 없기 때문에 발생한 문제라고 생각하면 된다.
이를 해결하기 위해서 약간의 trick을 주도록 하자.
import "@/styles/globals.css";
import Layout from "@/components/Layout";
export default function App({ Component, pageProps }) {
const EmptyLayout = ({children}) => <>{children}</>
const SubLayout = Component.Layout || EmptyLayout
return (
<div>
<Layout>
<SubLayout>
<Component {...pageProps} />;
</SubLayout>
</Layout>
</div>
)
}
SubLayout
은 Component
가 Layout
property가 없다면 EmptyLayout
을 갖도록 한다. EmptyLayout
은 그대로 children
을 반환하도록 하는 객체로 아무런 기능도 없는 객체이다.