필요한 부분만 스윽. 봐도 전체를 다 알기는 어렵다.
추가 공부 필요
npm install next react react-dom
"scripts": {
"dev": "next",
"build": "next build", // 빌드 for production
"start": "next start" // run production server
}
파일라우팅 : 주소매핑
js, ts, tsx 다 됨
/pages/index.js : /
/pages/Banana.ts : /Banana
/pages/sub/Lanana.tsx : /sub/Lanana
/pages/dyna/[dynamic].js: /dyna/동적Path받기 // 동적으로 path 받을 수 있음
기본, 모든 페이지를 렌더. CSR 이 모든 자바스크립트를 가져야 실행되므로 그보다 빠름.
종류는 다음 두 가지. 이건 요청 때마다 새로운 데이터가 매번 필요하나 아니냐에 따라 결정.
데이터 없는 정적생성은 그냥 생성.
데이터 있는 정적생성은 getStaticProps, getStaticPaths 등 사용.
처음에 필요한 자료를 빌드
때 다 불러와서 넣어놓고 그것만 사용.
getServerSideProps
하면. 매 요청
당 실행됨.
export async function getStaticProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}
파일구조: url 입력
/pages/dyna/[dynamic].js: /dyna/동적인값
담기는 형식
context.dynamic : '동적인값
빌드 타임 때 정적으로 렌더링할 경로 설정.
여기서 정의하지 않은 하위 경로는 접근해도 화면이 안 뜸.
동적라우팅 할 때, 라우팅 되는 경우의 수를 하나하나 집어넣을 것.
/pages/dyna/[dynamic].js: /dyna/동적인값
// This function gets called at build time
export async function getStaticPaths() {
return {
//빌드 타임 때 아래 정의한 /dyna/1, /dyna/2, ... /dyna/동적인값 경로만 pre렌더링.
paths: [
{ params: { dynamic: 1 } },
{ params: { dynmic: 2 } }
......
{ params: { dynmic: 동적인값 } }
],
// 만들어지지 않은 것도 추후 요청이 들어오면 만들어 줄 지 여부.
fallback: true,
}
}
export async function getServerSideProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}
context 하위 키.
빈번하게 자료 업데이트가 필요하면 pre-render 하지 않아도 됨, 클라이언트 사이드에서 해.
대시보드의 경우 Private 하기 땜시 잘 통함. SEO 필요 없어서.
SWR 이라는 데이터 fetching 훅 사용 강추.
import useSWR from 'swr'
function Profile() {
const { data, error } = useSWR('/api/user', fetch)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
pages/_app.js.
요게 기본 app 설정을 override 하는 커스텀 app 페이지다. 여기다가 import 하면 전역으로 다 import 가 적용됨. 프로덕션 빌드 때는 하나의 .css 파일로 미니파이드됨.
여기 임포트 한 건 다른곳에서 넣지 말 것. 충돌남.
styles.css:
body {
font-family: 'SF Pro Text', 'SF Pro Icons', 'Helvetica Neue', 'Helvetica',
'Arial', sans-serif;
padding: 20px 20px 60px;
max-width: 680px;
margin: 0 auto;
}
import '../styles.css'
// This default export is required in a new `pages/_app.js` file.
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
CSS Modules using the [name].module.css file naming convention.
로컬 스콥 CSS 적용될 유닉클래스 이름 만들어 주기 때문에 어느 컴포넌트에서나 충돌 걱정 없이 로드할 수 있음.
Regular stylesheets and global CSS files are still supported.
components/Button.module.css
/*
You do not need to worry about .error {} colliding with any other `.css` or
`.module.css` files!
*/
.error {
color: white;
background-color: red;
}
import styles from './Button.module.css'
export function Button() {
return (
<button
type="button"
// Note how the "error" class is accessed as a property on the imported
// `styles` object.
className={styles.error}
>
Destroy
</button>
)
}
CSS-in-JS 솔루션 사용 가능함.
function HiThere() {
return <p style={{ color: 'red' }}>hi there</p>
}
export default HiThere
그리고 넥스트 팀이 번들링 한 scoped CSS 위해 styled-css 란 걸 이용가능.
function HelloWorld() {
return (
<div>
Hello world
<p>scoped!</p>
<style jsx>{`
p {
color: blue;
}
div {
background: red;
}
@media (max-width: 600px) {
div {
background: blue;
}
}
`}</style>
<style global jsx>{`
body {
background: black;
}
`}</style>
</div>
)
}
export default HelloWorld
.scss and .sass .module.scss or .module.sass
지원.
npm install sass
플로그인 설치
@zeit/next-less
@zeit/next-stylus
public
폴더에 넣으면 /
주소로 접근 가능.
public/my-image.png
function MyImage() {
return <img src="/my-image.png" alt="my image" />
}
export default MyImage
투입된 곳에서 안 씀 건너뛰뛰
인덱스 라우팅
중첩 라우팅
동적 라우팅
패이지 끼리 링킹
<Link>
를 사용하면 클라이언트 사이드 라우팅이 됨.
import Link from 'next/link'
function Home() {
return (
<ul>
<li>
<Link href="/blog/[slug]" as="/blog/hello-world">
<a>To Hello World Blog post</a>
</Link>
</li>
</ul>
)
}
export default Home
라우터 주입
To access the router object in a React component you can use useRouter or withRouter.
pages/post/[pid].js
import { useRouter } from 'next/router'
const Post = () => {
const router = useRouter()
const { pid } = router.query
return <p>Post: {pid}</p>
}
export default Post
파일 경로가 pages/post/[pid].js
일 때
/post/abc: { "pid": "abc" }
/post/abc?foo=bar : { "foo": "bar", "pid": "abc" }
근데 다이나믹 파일 명(pid)을 쿼리스트링에 써버리면 override 되면서 쿼리파라미터만 남음
/post/abc?pid=123: { "pid": "abc" }
삼점으로 모든 경로 캐치 가능.
/docs/모든url 캐치하려면
pages/docs/[...slug].js
/post/a: { "slug": ["a"] }
/post/a/b: { "slug": ["a", "b"] }
결과를 보면 사실상 깊이에 상관없이 모든 route 명을 꺼내서 배열로 만드는 기능이라고 봐야...
사전 정의된 라우트 > 동적 라우트 > 캐치올 라우트
끙. hydrate 까지 보기는 ㅠㅠ 나중에 필요할 때 보자.
Pages that are statically optimized by Automatic Static Optimization will be hydrated without their route parameters provided, i.e query will be an empty object ({}).
After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object.
<Link>
없이 클라이언트 사이드에서 직접 라우팅 할 수 있음.
import Router from 'next/router'
function ReadMore() {
return (
<div>
Click <span onClick={() => Router.push('/about')}>here</span> to read more
</div>
)
}
export default ReadMore
getInitialProps 가 9.3 이상부터는 매번 실행되는 것과 한 번 실행되는 걸로 분화가 되었으니 이건 getInitilaProps 를 사용하는 경우에만 유효한 이야기
getInitialProps
실행 없이 URL 바꿀 수 있는게 얕은 라우팅.
라우터 객체를 통해서 상태 손실 없이 pathname
query
을 받을 수 있다.
To enable shallow routing, set the shallow option to true. Consider the following example:
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// Current URL is '/'
function Page() {
const router = useRouter()
useEffect(() => {
// Always do navigations after the first render
router.push('/?counter=10', null, { shallow: true })
}, [])
useEffect(() => {
// The counter changed!
}, [router.query.counter])
}
export default Page
페이지에 라우터 객체 만들기 싫으면 직접 사용해도 됨.
import Router from 'next/router'
// Inside your page
Router.push('/?counter=10', null, { shallow: true })
브라우저 주소는 /?counter=10
로 변하지만 라우트의 상태만 변할 뿐이고 페이지는 대체되지 않는다.
자꾸 맥락이 SSR, 그것도 매번 실행되는 데이터 조작인 getInitialProps 라는 걸 잊게된다. 이 맥락에서 문서를 봐야 이해가 된다.
URL 변경을 componentDidUpdate 로 감시할 수도 있다.
componentDidUpdate(prevProps) {
const { pathname, query } = this.props.router
// verify props have changed to avoid an infinite loop
if (query.counter !== prevProps.router.query.counter) {
// fetch data based on the new query
}
}
얕은 라우팅은 같은 페이지 URL 내에서 만 작동한다.
Router.push('/?counter=10', '/about?counter=10', { shallow: true })
아무리 쉘로우:true
라고 해줘도, 파라미터가 바뀐 게 아니고, 페이지 자체가 바뀌었으므로 현재 페이지를 unload 하고 새 페이지를 load 하는 과정에서 getInitialPrpos
부분이 실행된다. 세 페이지니까.
넥스트 서버에서 API
기능을 제공할 수 있게 해주는 솔루션.
pages/api
안에 넣어두면 url 로 접근할 때는 바로 호스트 루트가 api 폴더로 매칭됨.
파일구조: url
pages/api/*
: http(s)://호스트/api/*
pages/api/사용자목록
: http(s)://호스트/api/사용자목록
구조는 이럼
pages/api/user.js
export default (req, res) => {
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ name: 'John Doe' }))
}
요청 핸들러 함수를 export default
처리.
API 경로에서 다른 HTTP 메소드를 처리하고 싶으면, req.method
사용
export default (req, res) => {
if (req.method === 'POST') {
// Process a POST request
} else {
// Handle any other HTTP method
}
}
API 라우팅은 CORS 헤더를 설정 안함, 즉 기본적으노 같은 오리진만 가능하다. [마이크로 CORS]https://nextjs.org/docs/api-routes/api-middlewares#micro-support)로 이거 설정 가능함.
동적 경로 라우팅과 비슷하게 API 라우팅도 동적으로 가능.
pages/api/post/[pid].js
[pid] 부분을 쿼리 객체로 뽑아서 사용 가능.
export default (req, res) => {
const {
query: { pid },
} = req
res.end(`Post: ${pid}`)
}
요청 주소 => 응답 결과
/api/post/abc => Post: abc
일반 라우팅과 비슷하게.. API 도 하위의 모든 요청을 하나의 핸들러 감수에서 처리하게 할 수 있음.
페이지구조: 대응 주소
pages/api/post/[...slug]:
/api/post/a
/api/post/a/b
/api/post/a/b/c
slug 대신 param 이라고 쓸 수 있음
요청주소: 캐치올객체 결과
/api/post/a: { "slug": ["a"] }
/api/post/a/b: { "slug": ["a", "b"] }
구현은 요렇게 됨.
export default (req, res) => {
const {
query: { slug },
} = req
res.end(`Post: ${slug.join(', ')}`)
}
사전 정의된 API 라우트 > 동적 API 라우트 > 캐치올 API 라우트
req 해석하는, 빌트인 된 미들웨어.
req.cookies - 요청에 쿠키 보내졌는지 담는 객체. 기본 {}
req.query - 쿼리스트링 포함하는 객체. 기본 {}
req.body - 컨텐트 타입이별로 해석된 body를 담는 객체 body 없으면 null
모든 API 라우트는 기본 설정을 변경하기 위해서 config
객체를 export 할 수 있다.
export const config = {
api: {
bodyParser: {
sizeLimit: '1mb',
},
},
}
api 객체는 API 라우트에 가능한 모든 설정을 포함함.
bodyParser 는 body parsing 활성화함, Stream으로 처리하고 싶으면 비활성화 가능
export const config = {
api: {
bodyParser: false,
},
}
bodyParser.sizeLimit 는 해석된 body 에 담길 최대 사이즈. byte 단위.
export const config = {
api: {
bodyParser: {
sizeLimit: '500kb',
},
},
}
적합한 미들웨어를 connect 연결할 수 있다.
혹은 아래처럼 Promise 감싸고 그걸 await 로 기다리게 하는 req 핸들러를 만들면 된다.
API 엔드포인트 위해서 CORS 설정 필요할 경우 cors 패키지를 이용할 수 있다
import Cors from 'cors'
// Initializing the cors middleware
const cors = Cors({
methods: ['GET', 'HEAD'],
})
// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
function runMiddleware(req, res, fn) {
return new Promise((resolve, reject) => {
fn(req, res, result => {
if (result instanceof Error) {
return reject(result)
}
return resolve(result)
})
})
}
// 요청 핸들러는
async function handler(req, res) {
// 미들웨어 실행하고 기다린다.
await runMiddleware(req, res, cors)
// 이제 처리
res.json({ message: 'Hello Everyone!' })
}
export default handler
export default (req, res) => {
res.status(200).json({ name: 'Next.js' })
}
헬퍼들
건너뜀.
이 부분은 9.3 이상에서만 유효함
이 부분 내용 왜 이렇게 길지? 이게 필요한가? 음? 일단 보자. 이거 지금 내가 투입될 작업에 필요 없는 거 같은데.. 음??? 아리까리하네.
getStaticProps 와 getStaticPaths 사용해서 사전렌더링을 서버 빌드 타임 때 (Static Generation) 하는 걸 앞서 봤다.
화면 표시 책임은 React SSR 이 책임지고, 자료 관리는 headless CMS 에서 받아온다면. SSR 은 좋다.
근데 만약 사용자가 draft 된 글을 지금 당장 보고 싶을 때는, 이미 정적으로 생성된 자료려 빌드된 게 아닌, 요청시에 draft 된 자료를 받아와서 렌더링을 하길 원한다.
이 때는 넥스트의 정적 사이트 생성을 우회하고 싶을 거다.
그런 기능이 있다. Preview Mode 라고.
post/12 은 SSR 빌드시간에 만들어 진 거. 근데 지금 빌드시에 만들어 지지 않았지만 headless cms 서버에는 들어있는 자료인 draft 상태인 15번 글을 보고 싶으면.
post/15
요청 시 글 보여주기 컴포넌트에 새로운 자료를 받아와서 다시 생성해서 내려 받기를 원할 거다.
getStaticProps 는 빌드 타임때만 실행되므로, 요청시에 새로 렌더링 되는 걸 못함. 그래서 이런 특수한 때만 다시 렌더링 하도록 우회하는 게 preview 모드다.
흐름은 이렇다.
미리보기 요청 -> 우회설정 -> 글보기 페이지 리다이렉트 -> 우회설정때 받아온 자료를 가지고 렌더링 -> 받아보기
미리보기 API 라우트 만든다. 예. pages/api/preview.js
응답 객체에서 setPreviewData
를 호출한다. setPreviewData
의 인자는 객체여야 한다. 그건 getStaticProps
에서 사용될 수 있다.
export default (req, res) => {
res.setPreviewData({})
res.end('Preview mode enabled')
}
res.setPreviewData
는 preview 모드를 켜는 브라우저 쿠키를 설정한다. 이 쿠키를 포함하는 모든 넥스트 JS 요청은 preview 모드
로 간주된다. 동시에 정적 생성페이지 행위가 변한다.
브라우저 개발자 도구 보면, 요청에 __prerender_bypass
, __next_preview_data
쿠키가 설정되 있는 걸 보게 될 거다.
Headless CMS 에 안전하게 접근하기
시크릿 토큰이랑 커스텀 프리뷰 URL 필요.
https://<your-site>/api/preview?secret=<token>&slug=<path>
<your-site>
배포 도메인.<token>
생성된 시크릿 키.<path>
미리보기 원하는 페이지. /posts/foo
라면, &slug=/posts/foo
.흐름
preview 요청하면, 시크릿 키 체크하고,res.setPreviewData
호출해서 preview Mode 켜는 쿠키 설정. 이후 자료 요청 & 렌더링해서 보여줄 페이지 경로로 리다이렉트
export default async (req, res) => {
// Check the secret and next parameters
// 클라이언트에 노출되지 않고, 나와 Headless CMS 만 아는 시크릿 키
if (req.query.secret !== 'MY_SECRET_TOKEN' || !req.query.slug) {
return res.status(401).json({ message: 'Invalid token' })
}
// Fetch the headless CMS to check if the provided `slug` exists
// getPostBySlug would implement the required fetching logic to the headless CMS
// 15번 draft 자료를 headlessCMS 에 요청할 수도 있음
const post = await getPostBySlug(req.query.slug)
// If the slug doesn't exist prevent preview mode from being enabled
if (!post) {
return res.status(401).json({ message: 'Invalid slug' })
}
// Enable Preview Mode by setting the cookies
res.setPreviewData({})
// Redirect to the path from the fetched post
// 직접 req.query.slug 이동하지 않는 건 리다이렉트 취약성이 발생할까봐.
res.writeHead(307, { Location: post.slug })
res.end()
}
307 쓰는 이유 : https://perfectacle.github.io/2017/10/16/http-status-code-307-vs-308/ 요거 함 봐야겠네.
res.setPreviewData
쿠기 설정이 되면, getStaticProps
는 빌드타임뿐 아니라 요청 시
에도 호출된다. 즉, 우회가 됨.
구제적으로 context 를 통해 호출을 할 수 있다.
context.preview
will be true.context.previewData
에setPreviewData
에 전달된 인자가 담겨있다.예를 들어서, 세션 데이터가 필요할 때면, res.setPreviewData({})
에서 {]에다가 세션 정보를 pass 할 수도 있다.
getStaticPaths
쓰면, context.params
도 사용 가능.
export async function getStaticProps(context) {
// If context.preview is true, append "/preview" to the API endpoint
// to request draft data instead of published data. This will vary
// based on which headless CMS you're using.
const res = await fetch(`https://.../${context.preview ? 'preview' : ''}`)
// ...
}
미리보기 자료 fetch
context.preview
context.previewData
에 따라 getStaticProps
는 다른 자료를 가져올 수 있다.
아래처럼 preview 여부에 따라서 cms에 자료를 요청하는 주소를 바꿔서 보낼 수도 있다.
export async function getStaticProps(context) {
// If context.preview is true, append "/preview" to the API endpoint
// to request draft data instead of published data. This will vary
// based on which headless CMS you're using.
const res = await fetch(`https://cms주소자료요청주소/${context.preview ? 'preview' : ''}`)
// ...
}
res 로 SSR 된 페이지가 내려오면 그것이 preview 를 담고 있는 페이지.
Take a look at the following examples to learn more:
DatoCMS Example (Demo)
Clear the preview mode cookies
쿠기 끝 날짜가 없어서 브라우저 꺼야 끝난다. clearPreviewData
로 강제 끝낼 수 있다.
export default (req, res) => {
// Clears the preview mode cookies.
// This function accepts no arguments.
res.clearPreviewData()
// ...
}
Specify the preview mode duration
setPreviewData
는 두번째 매개변수로 옵션 객체를 받음 :
maxAge: Specifies the number (in seconds) for the preview session to last for.
setPreviewData(data, {
maxAge: 60 * 60, // The preview mode cookies expire in 1 hour
})
previewData size limits
setPreviewData
로 getStaticProps
에 객체를 넘겨줄 수 있지만 어찌됐건 쿠키에 저장이 되기 때문에 2KB 최대다.
Unique per next build
previewData
를 암호화하는데 사용되는 bypass cookie value 와 private key 는 매 next build 마다 바뀐다. 추측을 불가능하게 하기 위해서.
Next.js supports ES2020 dynamic import(). They also work with SSR.
관리 가능한 chunk 로 코드를 split 하는 또다른 방법이다.
모듈 ../components/hello
은 페이지에 의해서 동적 로딩된다. export default
로 내보내진 컴포넌트를 가져온다.
import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(() => import('../components/hello'))
function Home() {
return (
<div>
<Header />
<DynamicComponent />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
export default
가 아니라 이름으로 내보내진 컴포넌트를 가져오려면 then 을 쓴다. import 가 promise 를 리턴해서 가능하다.
export function Hello() {
return <p>Hello!</p>
}
import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(() =>
import('../components/hello').then(mod => mod.Hello)
)
function Home() {
return (
<div>
<Header />
<DynamicComponent />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
다이나믹 컴포넌트 로딩 중에 보여질 로딩 컴포넌트를 옵션으로 넣을 수 있다.
import dynamic from 'next/dynamic'
const DynamicComponentWithCustomLoading = dynamic(
() => import('../components/hello'),
{ loading: () => <p>...</p> }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithCustomLoading />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
클라에서만 사용되는 라이브러리는 SSR 때 로그 안하고 싶을 거다.
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
넥스트는 페이지가 블록킹 데이터 요청을 가지고 있지 않으면 페이지가 (사전렌더링될 수 있는)정적이라고 판단한다. 페이지에서 getInitialProps
가 없으면 그렇다고 판단한다.
이게 주는 이득은, SSR 계산이 없다는 거다. 그래서 즉시 사용자에게 streamed 될 수 있다. 사용자는 ultra fast 로딩이다.
getInitialProps
있을 때. 페이지 요구되면 기본 작동한다(SSR)
getInitialProps
없으면, 페이지를 정적 HTML 으로 사전렌더링 해서 정적 최적화를한다. 이 단계에서 query
를 게종하지 않으면 라우터 query
객체는 비어지게 된다. hydration 뒤에 클라이언트 사이드에서 query
가 조작된다.
next build 는 .html 파일을 정적 최적화 페이지에 내보낸다.
.next/server/static/${BUILD_ID}/about.html
getInitialProps
있으면 자바스크립트가 된다.
.next/server/static/${BUILD_ID}/about.js
next export 는 HTML 로 내보내기 해준다. node.js 서버 없이 독립실행 할 수 있는.
내보내진 앱은 dynamic routes, prefetching, preloading and dynamic imports 등의 거의 모든 기능을 지원한다.
next export 는 prerendering all pages to HTML 하는데 exportPathMap 로 매핑이 된 경로만 html 로 렌더링한다.
페이지에 getInitialProps
없으면 next export
필요 없다. 자동 정적 최적화 덕분에.
"scripts": {
"build": "next build && next export"
}
npm run build
out 디렉토리에 생성된다.
건너뜀
export 시기 때, 페이지에 getInitialProps
가 실행된다. 작동하는 서버가 없기 때문에 context의 req 와 res 는 비어진다.
정적 export 때 HTML 동적 렌더가 가능하지 않다, next export
안 쓰면 정적 생성과 SSR 이 하이브리드 될 수 있다. pages section 참조.
건너뛰기. 모바일 전용 페이지 사용할 일 없다.
Next.js includes the next/babel. But if you want to extend the default Babel configs, it's also possible.
.babelrc file:
{
"presets": ["next/babel"],
"plugins": []
}
next/babal 프리셋은 아래의 프리셋을 다 포함하기 때문에 추가되면 안된다.
preset-env
preset-react
preset-typescript
plugin-proposal-class-properties
plugin-proposal-object-rest-spread
plugin-transform-runtime
styled-jsx
새로 추가하는 대신에, next/babel 하위에서 추가 설정을 할 수 있다.
{
"presets": [
[
"next/babel",
{
"preset-env": {},
"transform-runtime": {},
"styled-jsx": {},
"class-properties": {}
}
]
],
"plugins": []
}
The modules option on "preset-env" should be kept to false, otherwise webpack code splitting is disabled.
기본 행위
Next 기본 CSS 처리 행위에서 CSS 변수는 IE11을 위해 컴파일 되지 않는다.
CSS variables are not compiled because it is not possible to safely do so. If you must use variables, consider using something like Sass variables which are compiled away by Sass.
이런 과정을 커스텀 할 수 있는데.. 자세한 건 그 때 필요하면 하기로 함.
커스텀 서버 돌릴 수 있다. 근데 서버리스 함수와 자동 정적 최적화 같은 기능이 빠져버림.
// server.js
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/a') {
app.render(req, res, '/b', query)
} else if (pathname === '/b') {
app.render(req, res, '/a', query)
} else {
handle(req, res, parsedUrl)
}
}).listen(3000, err => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
server.js 는 바벨 통과 안함. 따라서 현재 node 버전에서 지원되는 문법만 쓸 것.
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}
넥스트 어플리케이션과 연결하기 위해서 server.js 에서 다음 구문이 있다.
const next = require('next')
const app = next({})
next import는 아래 함수를 수신한다
dev: Boolean
- Whether or not to launch Next.js in dev mode. Defaults to falsedir: String
- Location of the Next.js project. Defaults to '.'quiet: Boolean
- Hide error messages containing server information. Defaults to falseconf: object
- The same object you would use in next.config.js. Defaults to {}요래 해서 받는 app
은 Next.js 가 요청을 핸들할 수 있게 해줌.
If your project uses a custom server, this behavior may result in the same content being served from multiple paths, which can present problems with SEO and UX.
To disable this behavior and prevent routing based on files in pages, open next.config.js and disable the useFileSystemPublicRoutes config:
module.exports = {
useFileSystemPublicRoutes: false,
}
Note that useFileSystemPublicRoutes simply disables filename routes from SSR; client-side routing may still access those paths. When using this option, you should guard against navigation to routes you do not want programmatically.
You may also wish to configure the client-side Router to disallow client-side redirects to filename routes; for that refer to Router.beforePopState.
App
넥스트에서 App은 페이지 초기화 하는 컴포넌트. 이걸 사용하면 페이지 초기화를 오버라이드할 수 있음.
./pages/_app.js
// import App from 'next/app'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
// Only uncomment this method if you have blocking data requirements for
// every single page in your application. This disables the ability to
// perform automatic static optimization, causing every page in your app to
// be server-side rendered.
//
// MyApp.getInitialProps = async (appContext) => {
// // calls page's `getInitialProps` and fills `appProps.pageProps`
// const appProps = await App.getInitialProps(appContext);
//
// return { ...appProps }
// }
export default MyApp
Component prop 은 현재 활성된 페이지다. 라우트가 변경되면 Component 는 활성화된 페이지로 바뀐다. 또한, Component 에 보낸 props는 모두 해당 페이지의 props 가 된다.
pageProps는 페이지 를 위해 사전적재된 초기 프롭스 객체다. 페이지가 getInitialProps 를 쓰지 않으면 비어있다.
Document
현재 프로젝트에서 이거 사용 안 한다. 스킵
넥스트 페이지가 서라운딩 도큐먼트 마크업 디피니션을 스킵하게 해줌.
getInitialProps
써서 비동기 서버렌더링 데이터 사용 가능.
./pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
<Html>
, <Head />
, <Main />
and <NextScript />
are required for the page to be properly rendered.
ctx
객체는 getInitialProps
renderPage: Function - a callback that executes the actual React rendering logic (synchronously). It's useful to decorate this function in order to support server-rendering wrappers like Aphrodite's renderStatic
Document
는 오직 서버에서만 렌더링된다. 이벤트 헨들러 onClick
같은 건 작동 안함.<Main />
바깥의 리액트 컴포넌트는 브라우저에 의해서 초기화 안 됨. 애플리케이션 로직을 거기다가 넣지 말기. 모든 페이지 간 공유 컴포넌트가 있다면(메뉴나 툴바), App
컴포넌트에 넣는 걸 추천.Document
getInitialProps
은 클라이언트 사이드 트랜지션에는 호출 안 되고 정적 최적화도 안 된다.next export
의 자동 정적 최적화
로 내보내진 정적 페이지는 getInitialProps
의 ctx.req / ctx.res
가 undefined
될 거다. 꼭 확인 바람.renderPage
커스터마이징
renderPage
커스터마이징은 서버사이드 렌더링을 적절하게 작동하기 위해서 css-in-js 라이브러리를 감싸야 할 필요하 있을 때만 되어야 함.
import Document from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const originalRenderPage = ctx.renderPage
ctx.renderPage = () =>
originalRenderPage({
// useful for wrapping the whole react tree
enhanceApp: App => App,
// useful for wrapping in a per-page basis
enhanceComponent: Component => Component,
})
// Run the parent `getInitialProps`, it now includes the custom `renderPage`
const initialProps = await Document.getInitialProps(ctx)
return initialProps
}
}
export default MyDocument
빈번하게 404 페이지가 접근된다. 그럼 Next.js 서버 부하 늘린다.
그래서 정적 404 를 제공한다.
빌드 타임때 정적 생성되는 pages/404.js
파일을 생성할 수 있다.
pages/404.js
export default function Custom404() {
return <h1>404 - Page Not Found</h1>
}
넥스트는 기본 404페이지 스타일로 500 오류 페이지도 제공한다. 이것은 정적 최적화기 안 됨. 왜냐면 서버 사이드에서 제출을 허용하기 때문이다. 그래서 404랑 분리가 되어있다.
Customizing The Error Page
pages/_error.js
function Error({ statusCode }) {
return (
<p>
{statusCode
? `An error ${statusCode} occurred on server`
: 'An error occurred on client'}
</p>
)
}
Error.getInitialProps = ({ res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404
return { statusCode }
}
export default Error
pages/_error.js is only used in production. In development you’ll get an error with the call stack to know where the error originated from.
Reusing the built-in error page
If you want to render the built-in error page you can by importing the Error component:
import Error from 'next/error'
import fetch from 'isomorphic-unfetch'
const Page = ({ errorCode, stars }) => {
if (errorCode) {
return <Error statusCode={errorCode} />
}
return <div>Next stars: {stars}</div>
}
Page.getInitialProps = async () => {
const res = await fetch('https://api.github.com/repos/zeit/next.js')
const errorCode = res.statusCode > 200 ? res.statusCode : false
const json = await res.json()
return { errorCode, stars: json.stargazers_count }
}
export default Page
The Error component also takes title as a property if you want to pass in a text message along with a statusCode.
src
Directory루트/pages
대신 루트/src/pages
로 사용할 수도 있다.
루트/pages
가 있으면 루트/src/pages
무시됨next.config.js
와 tsconfig.json
같은 설정 파일은 루트 디렉토리에 있어야 함, src로 옮겨도 작동 안 함. Same goes for the public directory패스
패스