August 24, 202210 minutes
안녕하세요?
Next.js가 유명해지면서 많은 분이 Next.js를 이용해서 웹 애플리케이션을 만들고 있는데요.
오늘은 Next.js의 치트 시트를 만들어 볼까 합니다.
시간 날 때 한 번씩 보면 많은 도움이 될 거로 생각합니다.
목차
Next.js 앱을 만드는 가장 쉬운 방식은 create-next-app을 사용하면 됩니다.
npx create-next-app@latest
또는
yarn create next-app
그러고 나서 아래 명령어로 개발 서버를 돌리면 되는데요. Next.js 개발 서버 주소는 가장 기본적인 3000 포트입니다. http://localhost:3000
npm run dev
또는
yarn dev
만약 create-next-app 명령어를 쓰지 않고 수작업으로 Next.js 앱을 만들고 싶으면 직접 아래와 같이 설치하시면 됩니다.
npm install next react react-dom
또는
yarn add next react react-dom
그러고 나서 package.json 파일에 아래 scripts 내용을 넣으시면 됩니다.
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
Next.js에서 정적 페이지를 만들려면 page 폴더 밑에 예를 들어 demo.js라는 이름으로 파일을 만들고 그 파일에 React 컴포넌트를 넣고 마지막으로 만들었던 React 컴포넌트를 export 하면 됩니다.
function Demo() {
return <h1>Demo</h1>
}
export default Demo
위에서 page 폴더 밑에 demo.js라는 이름으로 페이지를 만들었으면 개발서버에서의 주소는 바로 아래와 같습니다.
일명 페이지 방식 라우팅인 거죠.
http://localhost:3000/demo
Next.js에는 외부에서 데이터를 가져오는 방식이 여러 가지가 있는데요.
먼저 getServerSideProps함수를 이용한 방식이 있습니다.
getServerSideProps 함수는 이름에서도 알 수 있듯이 매 리퀘스트(request)마다 서버사이드 쪽에서 수행되는 함수인데요.
만약 여러분께서 페이지를 미리 렌더링 하고 싶을 때는 바로 getServerSideProps함수를 쓰면 됩니다.
export async function getServerSideProps(context) {
return {
props: {},
}
}
아래는 getServerSiteProps 함수를 이용해서 data를 클라이언트 사이드 쪽인 React 컴포넌트로 전달하는 예제입니다.
props라는 항목이 있는 객체를 리턴하면 됩니다.
function Page({ data }) {
// Code to render the `data`
}
export async function getServerSideProps() {
const res = await fetch(`https://.../data`)
const data = await res.json()
return { props: { data } }
}
export default Page
두 번째는 바로 getStaticPaths인데요.
말 그대로 정적 경로를 리턴하는 함수입니다.
getStaticProps를 통해 동적 경로의 데이터를 리턴할 때 해당 경로를 지정해 줘야 하는 게 바로 getStaticPaths입니다.
아래는 getStaticPaths 예제인데요.
paths 배열 항목이 있는 객체를 리턴하면 됩니다.
그리고 paths 배열에는 params 항목이 있는 객체가 있어야 하고요.
export async function getStaticPaths() {
return {
paths: [
{ params: { ... } }
],
fallback: true
};
}
세 번째가 바로 Next.js에서 가장 많이 쓰이는 getStaticProps 함수입니다.
이 함수는 빌드 타임에 서버사이드 데이터를 넘겨주는데요.
정적 사이트에 서버사이드 쪽에서 계산한 데이터를 심을 때 사용하는 함수입니다.
이 함수도 props 항목이 있는 객체를 리턴하면 됩니다.
export async function getStaticProps(context) {
return {
props: {},
}
}
아래 예제는 getStaticProps 함수를 이용해서 데이터를 가져왔을 때 그걸 클라이언트 사이드 쪽인 React 컴포넌트에서 사용하는 방식입니다.
function BlogPosts ({ posts }) {
return (
<>
<h1>{post.title}</h1>
))}
</>
)
}
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
}
}
export default BlogPosts
마지막으로 Next.js 최신 버전에 추가된 Incremental Static Regeneration(ISR) 방식이 있는데요.
getStaticProps 함수를 이용해서 빌드 타임에 가져온 데이터를 일정 시간마다 갱신하는 방식입니다.
예를 들어 10초라고 하면 한 명의 사용자가 페이지를 리퀘스트(request)하고 10초 후에 다시 또 다른 사용자가 페이지를 리퀘스트(request)했을 때 페이지의 데이터를 다시 불러오는 방식입니다.
만약, 10초 후 리퀘스트(request)가 없으면 페이지는 예전 그대로를 유지하게 됩니다.
ISR를 하려면 아래와 같이 getStaticProps 함수에 revalidate 항목을 추가하면 됩니다.
export async function getStaticProps(context) {
return {
props: {},
revalidate: 5 // 5초마다 페이지 갱신
}
}
보통 React에서 하는 방식인 useEffect 훅을 이용하면 됩니다.
function User() {
const [data, setData] = useState(null)
const [isLoading, setLoading] = useState(false)
useEffect(() => {
setLoading(true)
fetch('api/user-data')
.then((res) => res.json())
.then((data) => {
setData(data)
setLoading(false)
})
}, [])
if (isLoading) return <p>Loading user data...</p>
if (!data) return <p>No user data found</p>
return (
<h1>{data.name}</h1>
</div>
)
}
아니면 Next.js 는 SWR 라이브러리를 제공해주는데요.
SWR 라이브러리는 캐싱(caching), 리 밸리데이션(revalidation), 포커스 트래킹(focus tracking), 리 페칭(refetching on intervals) 등 다양한 기능을 제공합니다.
import useSWR from 'swr'
const fetcher = (...args) => fetch(...args).then((res) => res.json())
function User() {
const { data, error } = useSWR('/api/user-data', fetcher)
if (error) return <div>Failed to load user data</div>
if (!data) return <div>Loading user data...</div>
return (
<h1>{data.name}</h1>
</div>
)
}
Next.js에서 스타일을 적용하는 방법은 여러 가지가 있는데,
첫 번째, 글로벌 스타일링이 있습니다.
pages/_app.js 파일에 글로벌하게 사용할 styles.css 파일을 로드하면 이 스타일이 모든 페이지에 적용되는 방식입니다.
import '../styles.css'
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
컴포넌트 방식의 CSS가 있는데요.
[name].module.css라고 이름 지어서 각각의 컴포넌트마다 따로 import 하면 됩니다.
// Button.module.css
.error {
color: white;
background-color: red;
}
// Button.jsx
import styles from './Button.module.css'
export function Button() {
return (
<button
type="button"
className={styles.error}
>
Cancel
</button>
)
}
SASS를 사용하려면 먼저 sass 패키지를 설치해야 합니다.
npm install --save-dev sass
그러고 나서 next.config.js에 아래와 같이 SASS설정을 추가하면 됩니다.
const path = require('path')
module.exports = {
sassOptions: {
includePaths: [path.join(__dirname, 'styles')],
},
}
Next.js의 장점 중 하나가 바로 이미지를 최적화할 수 있는 Next.js 고유의 Image 컴포넌트인데요.
아래와 같이 기본 설치되어 있는 Image 컴포넌트만 불러와서 쓰면 됩니다.
import Image from 'next/image'
const myLoader = ({ src, width, quality }) => {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
const MyImage = (props) => {
return (
<Image
loader={myLoader}
src="me.png"
alt="Picture of the author"
width={500}
height={500}
/>
)
}
Next.js는 inline CSS에서 불러온 폰트를 자동으로 최적화시킵니다.
만약에 구글 웹 폰트 같은 걸 불러오려면 아래와 같이 pages/_document.js 파일을 수정하면 됩니다.
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<link
href="https://fonts.googleapis.com/css2?family=Inter&display=optional"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
package.json 파일에 아래와 같이 추가하면 자동으로 ESLint를 할 수 있습니다.
"scripts": {
"lint": "next lint"
}
만약 Next.js가 설치되어 있지 않은 monorepo를 사용 중이라면 아래와 같이 .eslintrc 파일에 추가하면 됩니다.
{
"extends": "next",
"settings": {
"next": {
"rootDir": "packages/my-app/"
}
}
}
ESLint와 같이 Prettier를 사용하고 싶을 때는 아래와 같이 관련 패키지를 설치하고
npm install --save-dev eslint-config-prettier
또는
yarn add --dev eslint-config-prettier
그리고 .eslintrc 설정 파일에 다음과 같이 추가하면 됩니다.
{
"extends": ["next", "prettier"]
}
Next.js 앱을 타입스크립트로 작성하고 싶으면 create-next-app을 실행할 때 –-ts 옵션이나 –-typescript 옵션을 추가하면 됩니다.
npx create-next-app@latest --ts
또는
yarn create next-app --typescript
만약 기존에 자바스크립트로 작성된 Next.js앱에 타입스크립트를 추가하려고 하려면 최상위 폴더에 tsconfig.json 파일만 만들고 개발서버 한 번만 돌려주면 Next.js가 알아서 나머지 설정을 안내해 줄 겁니다.
HTML에 원래 있던 <script>
엘러먼트는 Next.js에서 next/script 컴포넌트로 대체되었는데요.
아래 예제처럼 사용하시면 됩니다.
import Script from 'next/script'
export default function Home() {
return (
<>
<Script src="https://www.google-analytics.com/analytics.js" />
</>
)
}
<Script>
컴포넌트는 옵션이 3가지가 있는데 아래와 같습니다.
beforeInteractive: 페이지가 활성화되기 전에 스크립트를 로딩 afterInteractive: 페이지가 활성화되면 그 즉시 스크립트를 로딩 lazyOnload: idle 시간에 스크립트 로딩
<Script
src="https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.js"
strategy="beforeInteractive"
/>
Next.js는 파일 베이스 라우팅을 제공하는데요.
index.js 파일을 이용한 방식도 사용할 수 있습니다.
예를 들어 pages/index.js 파일은 라우팅 주소가 ‘/‘가 되고, pages/blog/index.js 파일은 주소가 ‘/blog’가 됩니다.
또 그냥 파일 이름만 적어도 그게 주소가 되는데요.
pages/blog/my-post.js 파일을 만들었으면 주소가 바로 ‘/blog/my-post’가 됩니다.
그리고 다이내믹(동적) 라우팅도 제공해주는데요.
pages/[username]/settings.js 파일을 만들었다면 동적 항목인 username이 만약 ‘john’이라면, 주소가 ‘john/settings’가 됩니다.
그리고 Next.js는 클라이언트 사이드 쪽에서의 라우팅을 위해 <Link>
컴포넌트를 제공해줍니다.
아래와 같이 사용하시면 됩니다.
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
다이내믹(동적) 경로일 경우 아래와 같이 스트링 리터럴을 사용하시면 훨씬 쉽습니다.
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
Next.js는 API 라우팅을 제공해주느데요.
pages/api 폴더 밑에 있는 파일 이름은 주소가 ‘/api/***’ 방식으로 매칭 되고 흔히 얘기하는 자체 API 경로로 사용할 수 있습니다.
API 라우팅을 만들려면 다음과 같이 /pages/api 폴더 밑에 적당한 이름의 파일을 만들고 그 파일에서 handler 함수를 export 하면 됩니다.
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
또는 아래와 같이 req.method를 이용해서 ‘POST’, ‘GET’, ‘DELETE’, ‘PUT’ 등 다양한 HTTP 메써드에 대응할 수 있습니다.
export default function handler(req, res) {
if (req.method === 'POST') {
// Process a POST request
} else {
// Handle any other HTTP method
}
}
Next.js에서 미들웨어를 사용하려면 Next.js 최신 버전이 필요합니다.
그러고 나서 _middleware.ts 파일을 /pages 폴더 밑에 만들고 export 하면 됩니다.
import type { NextFetchEvent, NextRequest } from 'next/server'
export function middleware(req: NextRequest, ev: NextFetchEvent) {
return new Response('Hello, world!')
}
아래 예제는 로깅 미들웨어 예제입니다.
import { NextRequest } from 'next/server'
// Regex for public files
const PUBLIC_FILE = /\.(.*)$/
export default function middleware(req: NextRequest) {
// Only log for visited pages
if (!PUBLIC_FILE.test(req.nextUrl.pathname)) {
// We fire and forget this request to avoid blocking the request until completion
// and let logging occur in the background
fetch('https://in.logtail.com', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.LOGTAIL_TOKEN}`,
},
body: JSON.stringify({
message: 'Log from the edge',
nested: {
page: req.nextUrl.href,
referrer: req.referrer,
ua: req.ua?.ua,
geo: req.geo,
},
}),
})
}
}
Next.js에서 인증하는 방법은 여러 가지가 있는데, 가장 일반적인 방법은 아래와 같습니다.
정적으로 생성되는 페이지에서의 인증은 클라이언트 상에서 스켈레톤 상태를 보여주다가 인증이 완료되면 페이지를 보여주는 방식입니다.
import useUser from '../lib/useUser'
import Layout from '../components/Layout'
const Profile = () => {
// Fetch the user client-side
const { user } = useUser({ redirectTo: '/login' })
// Server-render loading state
if (!user || user.isLoggedIn === false) {
return <Layout>Loading...</Layout>
}
// Once the user request finishes, show the user
return (
<h1>Your Profile</h1>
</Layout>
)
}
export default Profile
두 번째로 서버 사이드 쪽 인증 방식인데요.
아래 예제에서 보듯이 getServerSideProps 함수에서 아예 user 인증을 가지고 와서 그걸 클라이언트에 props로 전달하는 방식입니다.
서버 사이드에서 user가 인증이 안 되면 바로 redirect 되는 방식이라 인증 없이는 화면의 스켈레톤 상태도 보이지 않는 방식이죠.
import withSession from '../lib/session'
import Layout from '../components/Layout'
export const getServerSideProps = withSession(async function ({ req, res }) {
const { user } = req.session
if (!user) {
return {
redirect: {
destination: '/login',
permanent: false,
},
}
}
return {
props: { user },
}
})
const Profile = ({ user }) => {
// Show the user. No loading state is required
return (
<h1>Your Profile</h1>
</Layout>
)
}
export default Profile
Next.js에 쉬운 방식의 인증을 제공해 주는 툴이 많은데요.
대표적인 게 바로 Auth0, Firebase, Supabase가 있습니다.
그리고 NextAuth 패키지도 있으니까 설명서를 참고해 보면 됩니다.
테스트 방식도 여러가지가 있는데 대표적으로 Cypress 테스트를 하려면 아래와 같이 하면 됩니다.
Next.js 앱을 만들 때 cypress 지정하기
npx create-next-app@latest --example with-cypress with-cypress-app
또는 직접 cypress 패키지 설치
npm install --save-dev cypress
수동으로 직접 cypress 패키지를 설치했다면 아래와 같이 package.json 파일에 관련정보를 추가해야 합니다.
"scripts": {
...
"cypress": "cypress open",
}
cypress 실행은 다음과 같이 하면 됩니다.
npm run cypress
Cypress 테스트 파일을 만들려면 cypress/integration/app.spec.js 파일에 다음과 같이 테스트 내용을 적으면 됩니다.
describe('Navigation', () => {
it('should navigate to the about page', () => {
// Start from the index page
cy.visit('http://localhost:3000/')
// Find a link with an href attribute containing "about" and click it
cy.get('a[href*="about"]').click()
// The new url should include "/about"
cy.url().should('include', '/about')
// The new page should contain an h1 with "About page"
})
Jest를 이용해서 테스트하려면 다음과 같이 처음부터 제공해주는 파일을 이용하는 방식이 있고 직접 설치하는 방식도 있습니다.
npx create-next-app@latest --example with-jest with-jest-app
또는 직접 설치
npm install --save-dev jest @testing-library/react @testing-library/jest-dom
그리고 프로젝트 루트 폴더에 jest.config.js 파일을 다음과 같이 만들면 됩니다.
const nextJest = require('next/jest')
const createJestConfig = nextJest({
dir: './',
})
const customJestConfig = {
moduleDirectories: ['node_modules', '<rootDir>/'],
testEnvironment: 'jest-environment-jsdom',
module.exports = createJestConfig(customJestConfig)
package.json 파일에는 아래와 같이 설정해 주면 됩니다.
"scripts": {
...
"test": "jest --watch"
}
그리고 tests/index.test.jsx 파일에 테스트 내용을 작성하면 됩니다.
import { render, screen } from '@testing-library/react'
import Home from '../pages/index'
describe('Home', () => {
it('renders a heading', () => {
render(<Home />)
const heading = screen.getByRole('heading', {
name: /welcome to next\.js!/i,
})
expect(heading).toBeInTheDocument()
})
})
npm run test 명령어를 입력하면 Jest 테스트가 실행될겁니다.
지금까지 Next.js에 대해 가끔 헷갈릴 때 찾아볼 수 있는 완벽 가이드(치트 시트)를 작성해 봤습니다.
많은 도움이 됐으면 하네요.
그럼.