React 모델을 사용하면 페이지(컴포넌트)를 일련의 구성 요소로 분해할 수 있다.
페이지의 구성 요소 중에는 많은 부분이 각 페이지 간에 재사용되는 경우가 많다.
예를 들어 모든 페이지에 동일한 탐색 모음과 바닥글이 있을 수 있습니다.
이럴 때 layout.js를 사용하면 효율적이다.
// components/layout.js
import Navbar from './navbar'
import Footer from './footer'
export default function Layout({ children }) {
return (
<>
<Navbar />
<main>{children}</main>
<Footer />
</>
)
}
전체 어플리케이션에 대해 하나의 레이아웃만 있는 경우, 사용자 지정 앱을 만들고 레이아웃으로 애플리케이션을 래핑하여 레이아웃 구조를 적용시킬 수 있다.
<Layout />
구성 요소는 페이지를 변경할 때 재사용 되기 때문에 구성 요소 상태(예: 입력 값)가 유지됩니다.
// pages/_app.js
import Layout from '../components/layout'
export default function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}
여러 레이아웃이 필요한 경우 getLayout페이지에 속성을 추가하여 레이아웃에 대한 React 구성 요소를 반환할 수 있다.
getLayout은 함수를 반환하기 때문에 원하는 경우 복잡한 중첩 레이아웃을 가질 수 있다.
// pages/index.js
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
export default function Page() {
return {
/** Your content */
}
}
Page.getLayout = function getLayout(page) {
return (
<Layout>
<NestedLayout>{page}</NestedLayout>
</Layout>
)
}
// pages/_app.js
export default function MyApp({ Component, pageProps }) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || ((page) => page)
return getLayout(<Component {...pageProps} />)
}
Next.js의 Image 컴포넌트인 next/image
는 HTML의 <img>
요소의 확장으로서 모던 웹에 맞추어 향상된 기능을 제공한다.
next의 Image 컴포넌트에 내장되어 있는 성능 최적화는 웹의 성능을 끌어 올리는데에 도움을 준다.
Improved Performance 최신 이미지 형식을 사용하여 항상 각 장치에 대해 올바른 크기의 이미지를 제공한다.
Visual Stability 누적되는 레이아웃으로 인해 레이아웃이 자동으로 변경되는 것을 방지한다.
Faster Page Loads 이미지가 뷰포트에 들어갈 때만 로드되며 선택적인 블러업 자리 표시자와 함께 로드된다.
Asset Flexibility 원격 서버에 저장된 이미지의 경우에도 이미지 크기를 조정할 수 있다.
import Image from 'next/image'
import profilePic from '../public/me.png'
import시 Next.js가 분석할 수 있도록 Public 폴더 내에 저장되어야 한다.
Next.js는 가져온 파일을 기반으로 이미지를 width값을 자동 결정한다.
height 값은 이미지가 로드되는 동안 누적 레이아웃 이동을 방지하는 데 사용된다.
function Home() {
return (
<>
<h1>My Homepage</h1>
<Image
src={profilePic}
alt="Picture of the author"
// width={500} automatically provided
// height={500} automatically provided
// blurDataURL="data:..." automatically provided
// placeholder="blur" // Optional blur-up while loading
/>
<p>Welcome to my homepage!</p>
</>
)
}
원격 이미지를 사용할 때에는 src속성이 URL 문자열이어야 한다.
다만 Next.js는 빌드 프로세스 중에 원격 파일에 액세스할 수 없으므로
width, height, blurDataURLprops를 수동으로 제공해야 한다.
<Image
src="/me.png"
alt="Picture of the author"
width={500}
height={500}
/>
이미지가 큰 파일이라면(Large Contentful Paint) priority를 Image 컴포넌트의 속성을 추가해야 한다.
priority 속성이 추가된 Image 컴포는트는 Next.js가 로드할 이미지의 우선 순위를 특별히 지정할 수 있으므로 LCP 파일을 불러올 때 의미 있는 향상으로 이어진다.
<Image
src="/me.png"
alt="Picture of the author"
width={500}
height={500}
priority
/>
이미지의 크기 조정
이미지가 일반적으로 성능을 저하시키는 방법 중 하나는 이미지가 로드될 때 페이지의 다른 요소를 밀어내는 레이아웃 시프트다.
이미지가 레이아웃 변경시키는 행위를 피하는 방법은 항상 이미지 크기를 조정하는 것 입니다.
이를 통해 브라우저는 이미지가 로드되기 전에 이미지를 위한 충분한 공간을 정확하게 예약할 수 있습니다.
next/image
는 좋은 성능 결과를 보장하도록 설계 되었기 때문에 세 가지 방법 중 하나로 크기를 조정 해야 합니다 .
layout="fill"
을 사용하면 이미지가 자신의 부모 요소를 채우도록 확장됩니다.Image 컴포넌트에 layout='fill'
속성을 추가한다면, 이미지의 부모요소는 반드시 position:relatve
를 갖고 있어야 한다.
Image 컴포넌트에 layout='responsive'
속성을 추가한다면, 부모 요소는 블록 요소여야 한다.
Next.js에는 환경 변수에 대한 지원이 내장되어 있어 다음을 수행할 수 있다.
.env.local
: 환경 변수를 로드하는 데 사용한다
접두사로 환경 변수를 브라우저에 노출NEXTPUBLIC
// env.local
DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypassword
getStaticProps에서 사용
export async function getStaticProps() {
const db = await myDB.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS,
// 브라우저에 노출시키고 싶다면 NEXT_PUBLIC_* 사용
})
// ...
}
Next.js에는 페이지 개념을 기반으로 구축된 파일 시스템 기반 라우터가 있다.
파일이 pages
디렉토리에 추가되면 자동으로 경로로 사용할 수 있다.
디렉토리 내의 파일 pages은 가장 일반적인 패턴을 정의하는 데 사용하는 방법이다.
index router는 이름이 지정된 파일을 디렉터리 루트로 자동 라우팅한다.
pages/index.js
→ /
pages/blog/index.js
→ /blog
라우터는 중첩 파일을 지원한다.
중첩된 폴더 구조를 생성하는 경우 파일은 여전히 동일한 방식으로 자동으로 라우팅된다.
pages/blog/first-post.js
→ /blog/first-post
pages/dashboard/settings/username.js
→ /dashboard/settings/username
대괄호로 다이나믹한 url을 생성할 수 있다. [id]
router.query로 key와 value를 꺼낼 수 있다.
catch all routes with [...slug]하면 query의 key slug에 배열로 값들이 존재하게 된다.
optional catch all routes [[...slug]] 2중 배열로 감싸면 slug가 없는 경우에도 대응할 수 있다.
pages/blog/[slug].js
→ /blog/:slug( /blog/hello-world)
pages/[username]/settings.js
→ /:username/settings( /foo/settings)
pages/post/[...all].js
→ /post/*( /post/2020/id/title)
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
뷰포트의 모든 항목들에서 <Link />
를 사용하는 페이지에 대해 기본적으로 pre-fetch가 진행된다.(SSG)
서버 렌더링 경로(SSR)에 대한 데이터는 미리 가져오지 않는다.
또한 보간을 사용하여 path를 생성할 수 있다.
예를 들어 구성 요소에 props으로 전달된 게시물 목록을 표시하려면 아래와 같이 작성한다.
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
// 또는 href 속성을 다음과 같이 작성할 수도 있다.
<Link
href={{
pathname: '/blog/[slug]',
query: { slug: post.slug },
}}>
React 컴포넌트의 객체에 액세스하려면 useRouter를 사용할 수 있다.
Caveats
pages match는 정적인게 먼저 매칭된다
server side 코드 없이 빌드된 페이지는 router.query는 처음에는 빈 객체다.
hydration 이후 쿼리가 inject된다.
import Router, { useRouter } from 'next/router'
import { useEffect } from 'react'
export default function FirstPost () {
const router = useRouter();
useEffect(()=>{
router.push("/posts/first-post/?counter=10",undefined,{ shallow: true })
},[])
useEffect(()=>{
alert(router.query.counter)
},[router.query.counter])
// ...
}