- Published on
NextJS 1. Page & Route
- Author
- Name
- yceffort
์์ฆ ๋ฆฌ์กํธ๋ฅผ ์ฐ๋ ๋ง์ ํ๋ก์ ํธ์์, SSR์ ์ง์ํ๊ธฐ ์ํด nextjs๋ฅผ ์ฐ๊ณ ์๋ค. ์ด๊ธฐ ๋ก๋ฉ ์๋๋, SEO ์ง์ ์ด์ ๋ฑ ๋ฑ ๋๋ฌธ์ ์๋ฌด๋๋ SPA๋ ์์ฆ ํธ๋ ๋์์ ๋ง์ด ๋ฐ๋ฆฐ ๊ธฐ๋ถ์ด๋ค. ๋ฌผ๋ก razzle ์ ์ฐ๊ฑฐ๋ custom server ๋ก ๋งจ ๋ฐ๋ฅ์ ํด๋ฉํ๋ ๋ฐฉ๋ฒ๋ ์์ง๋ง ์ฌ๊ธฐ์ ๊ธฐ ์ปจํผ๋ฐ์ค๋ ์ฃผ๋ณ ์ฌ๋๋ค์ ๋ง์ ๋ค์ด๋ณด๋ฉด nextjs๊ฐ ๋์ธ์ด๊ธด ํ ๊ฒ ๊ฐ๋ค.
์ ์ฌ ์ด๋๋ก nextjs๋ฅผ ์ฐ๋ฉด์ ๋ณ ์๊ฐ ์์ด ์ผ๋ ๊ฒ๋ค์ด ๋ง์๋ฐ, 9.3 ์ถ์๋ฅผ ๊ธฐ๋ ํ์ฌ ์ด์ฐธ์ ํ๋์ฉ ์ ๋ฆฌํด๋ณด๋ ค๊ณ ํ๋ค.
Table of Contents
1. Page
๊ธฐ๋ณธ์ ์ผ๋ก, pages/ํ์ผ๋ช
.js|ts|tsx
๋ค์ด๋ฐ์ผ๋ก ํ์ผ์ ๋ง๋ค๋ฉด /ํ์ผ๋ช
์ผ๋ก ๋ผ์ฐํ
์ ํ ์ ์๋ค. pages/about.js
๋ก ํ์ผ์ ๋ง๋ค๋ฉด /about
์ผ๋ก ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
๋ค์ด๋๋ฏน ๋ผ์ฐํธ์ ๊ฒฝ์ฐ์๋ ๋น์ทํ๋ค. pages/๋๋ ํฐ๋ฆฌ๋ช
/[id].js|ts|tsx
๋ก ์์ฑํ๊ฒ๋๋ฉด, ๋๋ ํ ๋ฆฌ๋ช
/id
๋ก ์ ๊ทผ ๊ฐ๋ฅํ๋ค. ์๋ฅผ ๋ค์ด pages/posts/[id].tsx
๋ก ํ์ผ์ ์์ฑํ๋ฉด, posts/1
, posts/2
์ ๊ฐ์ ์์ผ๋ก ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
pages/posts/[id].tsx
import React from 'react'
import { useRouter } from 'next/router'
export default function Post() {
const router = useRouter()
const { id } = router.query
return <div>Post id {id}</div>
}
์์๋ก ์ ์ธํ id ๋ ์์ฒ๋ผ ๋ฐ์์ ์ฒ๋ฆฌํ ์ ์๋ค.
nested routes๋ ์์ ๋ง์ฐฌ๊ฐ์ง๋ก ์ฒ๋ฆฌํ๋ฉด ๋๋ค.
2. Routing
Nextjs์์๋ SPA์ ์ ์ฌํ ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ผ์ฐํ
์ ์ง์ํ๋ค. Link
๋ผ๊ณ ๋ถ๋ฆฌ๋ ์ปดํฌ๋ํธ๋ฅผ ํ์ฉํ๋ฉด, ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ผ์ฐํ
์ ํ ์ ์๋ค.
import Link from 'next/link'
function Home() {
return (
<Link href="/">
<a>Home</a>
</Link>
)
}
export default Home
nextjs ๋ Link
๋ฅผ ์ ์ ํ a ํ๊ทธ๋ก ๋ณํํด ์ค๋ค.
์์์ ์ธ๊ธํ ๋ค์ด๋๋ฏน ๋ผ์ฐํธ์ ๊ฒฝ์ฐ์๋, ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด ์กฐ๊ธ ๋ค๋ฅด๋ค. href
์ as
๋ฅผ ์ ๋ฌํด ์ฃผ์ด์ผ ํ๋ค.
href
: ๋๋ ํ ๋ฆฌ ๋ช ์ ๋๊ฒจ์ฃผ๋ฉด ๋๋ค./posts/[id]
as
: ๋ธ๋ผ์ฐ์ ์ ์ค์ ๋ก ํ์๋ ์ฃผ์๋ฅผ ๋๊ธด๋ค./posts/1
import Link from 'next/link'
function Home() {
return (
<ul>
<li>
<Link href="/posts/[id]" as="/posts/1">
<a>To Post</a>
</Link>
</li>
</ul>
)
}
export default Home
3. Router
nextjs์ ๋ผ์ฐํฐ ์์๋ ๋ค์๊ณผ ๊ฐ์ ์ ๋ณด๊ฐ ํฌํจ๋์ด ์๋ค.
pathname
: (String) ํ์ฌ ๋ผ์ฐํธquery
: (Object) object๋ก ํ์ฑํ query stringasPath
: (String) ์ค์ ๋ก ๋ธ๋ผ์ฐ์ ์ ํ์๋๊ณ ์๋ path
๊ทธ๋ฆฌ๊ณ ์๋์ ๊ฐ์ router api๋ ํฌํจ๋์ด ์๋ค.
3-1. Router Api
Router.push
ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ํธ๋์ง์ ์ ๋ค๋ฃฐ ๋ ์ฐ๋ api๋ค.
import Router from 'next/router'
Router.push(url, as, options)
url
: ์ด๋ํ URL์ ๋ช ์ํ๋ค. ๋ณดํตpage
๋ช ์ ๋ฃ๋๋คas
: ์ต์ ๋ ํ๋ผ๋ฏธํฐ๋ก, ๋ธ๋ผ์ฐ์ ์์ ๋ณด์ฌ์ง URL์ด๋ค. ์์ผ๋ฉด default๋กurl
์ด ๋ค์ด๊ฐ๋ค.options
: ์ shallow๋ง ์ต์ ์ผ๋ก ๊ฐ์ง ์ ์๋ค.shallow
:getInitialProps
๋ฅผ ์ฌ์คํํ์ง ์๊ณ ํ์ฌ ํ์ด์ง์ ๋ผ์ฐํธ๋ฅผ ์ ๋ฐ์ดํธ ํ๋ค. ๊ธฐ๋ณธ๊ฐ์ false๋ค.
๋ฌด์จ ์๋ฆฌํ๋์ง ๋ชจ๋ฅด๊ฒ ๋ค. ์์ ๋ก ์์๋ณด์.
index.tsx
import React from 'react'
import { useRouter } from 'next/router'
import { NextPageContext } from 'next'
export default function Index() {
const { push } = useRouter()
function pushOnlyUrl() {
push('/posts/1')
}
function pushWithAs() {
push('/posts/[id]?hello=world', '/posts/1')
}
function shallowPush() {
push('/?counter=1', undefined, { shallow: true })
}
function notShallowPush() {
push('/?counter=1')
}
function pushUrl() {
push('/about')
}
function pushUrlAndAs() {
push('/about', '/about')
}
return (
<>
<ul>
<li>
<button onClick={() => pushOnlyUrl()}>1๋ฒ. Push only URL</button>
</li>
<li>
<button onClick={() => pushWithAs()}>2๋ฒ. Push with as</button>
</li>
<li>
<button onClick={() => shallowPush()}>3๋ฒ. shallow push</button>
</li>
<li>
<button onClick={() => notShallowPush()}>
4๋ฒ. not shallow push
</button>
</li>
<li>
<button onClick={() => pushUrl()}>5๋ฒ. push route</button>
</li>
<li>
<button onClick={() => pushUrlAndAs()}>
6๋ฒ. push route with as
</button>
</li>
</ul>
</>
)
}
Index.getInitialProps = function (_: NextPageContext) {
console.log('getInitialProps of Index')
return {}
}
[id].tsx
import React from 'react'
import { useRouter } from 'next/router'
import { NextPageContext } from 'next'
export default function Post() {
const router = useRouter()
console.log('Router', JSON.stringify(router))
const { id } = router.query
return <div>Post id {id}</div>
}
Post.getInitialProps = function ({ req }: NextPageContext) {
console.log('getInitialProps of Post')
return {}
}
about.tsx
import React from 'react'
import { NextPageContext } from 'next'
export default function About() {
return <div>about page</div>
}
About.getInitialProps = function (_: NextPageContext) {
console.log('getInitialProps of about')
return {}
}
1๋ฒ ๋ฒํผ: getInitialProps๊ฐ ์๋ฒ์ ์ฐํ๋ค. ์๋ฒ์ฌ์ด๋์์ ์คํ๋์์์ ์์๊ฐ ์๋ค. 1๋ฒ ๋ฒํผ ๋์์ ์ฌ์ฉ์๊ฐ ๋ธ๋ผ์ฐ์ ์์ ์ฃผ์๋ฅผ ์น๊ณ ๋ค์ด์ค๋ ๊ฒ๊ณผ ๋์ผํ๋ค.
{
"pathname": "/posts/[id]",
"route": "/posts/[id]",
"query": {"id": "1"},
"asPath": "/posts/1",
"components": {
"/posts/[id]": {"props": {"pageProps": {}}},
"/_app": {}
},
"isFallback": false,
"events": {}
}
2๋ฒ ๋ฒํผ: getInitialProps๊ฐ ํด๋ผ์ด์ธํธ์ ์ฐํ๋ค. ํด๋ผ์ด์ธํธ ์ฌ์ด๋์์ ์คํ๋์์์ ์์๊ฐ ์๋ค. ๊ทธ๋ฆฌ๊ณ ๋ํ url์์ ๋ณด๋๋ ์ฟผ๋ฆฌ์คํธ๋ง์ด ์ฌ์ฉ์ ๋ธ๋ผ์ฐ์ URL์๋ ๊ฐ์ถฐ์ง ๊ฒ์ ์์ ์๋ค. ๊ทธ๋ฌ๋ Post ์ปดํฌ๋ํธ์์ ํด๋น ๊ฐ์ ๋ฐ์๋ค๊ฐ ์ธ ์ ์๋ค.
{
"pathname": "/posts/[id]",
"route": "/posts/[id]",
"query": {"hello": "world", "id": "1"},
"asPath": "/posts/1",
"components": {
"/": {"props": {"pageProps": {}}},
"/_app": {},
"/posts/[id]": {"props": {"pageProps": {}}}
},
"isFallback": false,
"events": {}
}
3๋ฒ ๋ฒํผ: index์ getInitialProps๊ฐ ์คํ๋๋ฉด์ ์ฟผ๋ฆฌ์คํธ๋ง์ด ๋ณํ๋ค.
4๋ฒ ๋ฒํผ: index์ getInitialProps๊ฐ ์คํ๋์ง ์๊ณ ์ฟผ๋ฆฌ์คํธ๋ง์ด ๋ณํ๋ค.
5๋ฒ๊ณผ 6๋ฒ ๋ฒํผ: ๋ค์ด๋๋ฏน ๋ผ์ฐํธ๊ฐ ์๋๊ธฐ ๋๋ฌธ์, ๋์์ด ๋์ผํ๋ค. (getInitialProps๊ฐ ํด๋ผ์ด์ธํธ์ ์ฐํ). ๊ทธ๋ฌ๋ ์ฌ์ฉ์๊ฐ ์ฃผ์๋ฅผ ์ง์ ์น๊ณ ๋ค์ด๊ฐ๋ค๋ฉด ์๋ฒ์ฌ์ด๋์ ์ฐํ ๊ฒ์ด๋ค.
Router.Replace
Replace๋ Push์ ๋ฐ๋ ํ๋ผ๋ฏธํฐ๋ ๋์ผํ์ง๋ง, ๋์๋ง ๋ค๋ฅด๋ค. ์ด๋ฆ์์ ์ ์ ์๋ ๊ฒ ์ฒ๋ผ Replace๋ URL์ ์๋ก์ด ์คํ์ ์์ง ์๋๋ค.
Router.beforePopState
๋ช ๋ช์ ๊ฒฝ์ฐ (ํนํ ์ปค์คํ ์๋ฒ๋ฅผ ์ฐ๋ ๊ฒฝ์ฐ) popsState ์์ฒญ์ ๋ฐ์์ ๋ผ์ฐํธ์์ ์ก์ ์ด ์ผ์ด๋๊ธฐ ์ ์ ๋ฌด์ธ๊ฐ๋ฅผ ํ๊ณ ์ถ์ ์ ์๋ค.
Window ์ธํฐํ์ด์ค์ popstate ์ด๋ฒคํธ๋ ์ฌ์ฉ์์ ์ธ์ ๊ธฐ๋ก ํ์์ผ๋ก ์ธํด ํ์ฌ ํ์ฑํ๋ ๊ธฐ๋ก ํญ๋ชฉ์ด ๋ฐ๋ ๋ ๋ฐ์ํฉ๋๋ค.
_app.tsx
function App({ Component, pageProps }: AppProps) {
const router = useRouter()
useEffect(() => {
router.beforePopState(() => {
console.log('beforePopState!!')
return true
})
return () => {
router.beforePopState(() => true)
}
}, [])
return <Component {...pageProps} />
}
next์ routing์ด ์๋, ์ฌ์ฉ์๊ฐ ํ์คํ ๋ฆฌ๋ฅผ ์ง์ ์กฐ์ํ๋ ํ์ (๋ค๋ก๊ฐ๊ธฐ, ์์ผ๋ก๊ฐ๊ธฐ ๋ฑ)๊ฐ ์ผ์ด๋ ๊ฒฝ์ฐ ํด๋น ๋ฉ์๋๊ฐ ํธ์ถ๋๋ค. ๋ง์ฝ false๋ฅผ ๋ฆฌํดํ ๊ฒฝ์ฐ, Router๋ popState
๋ฅผ ์ฒ๋ฆฌํ์ง ์๋๋ค. (์ฃผ์๋ ๋ฐ๋์ง๋ง ์๋ฌด ์ผ์ด ์ผ์ด๋์ง ์๋๋ค.)
Router.events
Router์์ ์ผ์ด๋๋ ๋ค์ํ ์ด๋ฒคํธ๋ฅผ ๊ฐ์ง ํ ์ ์๋ค.
์ฌ๊ธฐ์ url์ ๋ธ๋ผ์ฐ์ ์ ๋จ๋ url์ ์๋ฏธํ๋ค. ๋ง์ฝ as๋ฅผ ์ผ๋ค๋ฉด, ์ฌ๊ธฐ์ url๊ฐ์ as ๊ฐ์ด ๋ ๊ฒ์ด๋ค.
routerChangeStart(url)
: route๊ฐ ๋ณํ๊ธฐ ์์ํ ๋routerChangeComplete(url)
: route์ ๋ณํ๊ฐ ๋๋ฌ์ ๋routerChangeError(err, url)
: route๊ฐ ๋ฐ๋๋ ๊ณผ์ ์์ ์๋ฌ๊ฐ ๋๊ฑฐ๋, route ๋ก๋ฉ์ด ์ทจ์๋์์ ๋err.cancelled
: ๋ค๋น๊ฒ์ด์ ์ด ์ทจ์๋์๋์ง ์ฌ๋ถ
beforeHistoryChange(url)
: ๋ธ๋ผ์ฐ์ ํ์คํ ๋ฆฌ๊ฐ ๋ฐ๋๊ธฐ ์ ์hashChangeStart(url)
: ํด์ฌ๊ฐ์ด ๋ณํ ๋hashChangeComplete(url)
: ํด์ฌ๊ฐ์ด ๋ค ๋ณํ๊ณ ๋ ๋ค ์
useEffect(() => {
router.events.on('routeChangeStart', (as) => {
console.log('routeChangeStart', as)
})
}, [])