251126 책리뷰 소플 처음 만난 next.js
26 Nov 2025
p56 직접 next.js 앱 생성
npm init
npm install next@lastest react@lastest react-dom@lastest
- package.json
scripts
# next.js 를 개발모드로 실행
dev : next dev
# next.js 를 프로덕션 빌드
build : next build
# next.js 를 프로덕션 실행
start : next start
lint : next lint
- app/layout.tsx
import React from "react"
export default function RootLayout( children )
return(
<html lang="en">
<body>{children}</body>
</html>
)
- app/page.tsx
import React from "react"
export default function Page( )
return (
<h1>
hello world
</h1>
)
npm run dev
p67 create-next-app 사용해서 next.js 앱 생성
npx create-next-app@lastest
# 설정질문들 질문, 답변
- 폴더역할
- .next
- 빌드파일
- app
- app router 용 폴더
- .next
- 파일역할
- eslint.config.mjs
- eslint 설정파일
- next-dev.d.ts
- 타입설정
- next.config.ts
- 앱설정
- tsconfig.json
- 타입스크립트 설정
- globals.css
- layout.tsx
- page.module.css
- page.tsx
- eslint.config.mjs
npm run dev
p74 글로벌 스타일 설정, eslint 규칙변경
- app/global.css
.box {
padding: 8;
border: 2px solid black;
border-radius: 8;
}
.layout {
background-color: #00A2C7;
}
...
- eslint.config.mjs
const eslintConfig = ...
p94 앱라우터
- 최신버전의 next.js에서 표준방식으로 권장
- 파일이름
- layout
- page
- loading
- not-found
- error
- route
- template
- 계층구조
- Layout
- Template
- ErrorBoundary fallback=Error
- SuspendBoundary fallback=Loading
- ErrorBoundary fallback=NotFound
- Page
- ErrorBoundary fallback=NotFound
- SuspendBoundary fallback=Loading
- ErrorBoundary fallback=Error
- Template
- Layout
p98 URL 경로에 영향을 주지 않고 경로 구성
- 파일경로
- app/(content)/about/page.js
- url
- /about
p100 프로젝트 구성
- app
- account
- page.js /account
- avatar.js
- component
- button.js
- lib
- const.js
- api
- db.js
- route.js /api
- account
p102 동적 라우트
- 파일경로
- app/post/[postId]/page.tsx
- url
- /post/1
interface PageProps
params : { postId: string }
export default function Page(props:PageProps)
return <div>props.params.postId</div>
- 라우트와 params
- app/post/[postId]/page.js
- { postId : string }
- app/search/[..keyword]/page.js
- { keyword : string[] }
- app/[categoryId]/[itemId]/page.js
- { categoryId : string, itemId : string }
- app/post/[postId]/page.js
p114 링크와 네비게이션
- Link
export default function Page()
return <Link href="/home">home</Link>
- useRouter
- 클라이언트 컴포넌트에서 라우터를 사용해서 라우트를 변경
"use client"
export default function Page()
const router = useRouter()
return (
<button
type="button"
onClick={()=>{ router.push("/home") }}>
home
</button>
)
- redirect
- 서버컴포넌트에서 리디렉션
import {getSession} from "@/lib/authManager"
export default function Page()
const session = await getSession()
if (!session)
try
redirect("/", RedirectType.replace)
catch (error)
...
p168 정적 메타데이타
import type {MetaData} from "next"
export const metadata : MetaData
title : "타이틀"
description : "설명"
export default function Page()
...
p168 동적 메타데이타
type Props
params : { id : string }
export async function generateMetadata( props:Props )
const { id } = await props.params
const res = await fetch(`https://api.sample.com/posts/${id}`)
.then(res => res.json())
return {
title : post.title
description : post.description
openGraph : {
images : [...]
}
}
export default function Page(props:Props)
p189 동적 메타데이타 사용해보기
interface Props
params : Promise<{postId : string}>
export async function generateMetadata(props : Props) : Promise<Metadata>
const { postId } = await props.params
return {
title : `postId:${postId}`
description : `description:${postId}`
}
p193 동적으로 OpenGraph 이미지 생성
- app/api/og/route.tsx
export async function GET(req:NextRequest)
title = req.nextUrl.searchParams.get("title")
if(!title)
return NextResponse.json("title empty", {status:400})
return new ImageResponse(
(
<div
style=
>
{title}
</div>
{
width:1200
height:600
}
)
)
p206 앱라우터의 라우트 핸들러
- app/api/posts/route.ts
export async function GET( req:Request )
postList = await db.query.posts.findMany
return Response.json(postList)
export async function GET( req:Request )
searchParams = req.nextUrl.searchParams
offset = searchParams.get("offset")
limit = searchParams.get("limit")
p209 쿠키다루기
export async function GET(req:Request)
cookies = await cookies()
token = cookies.get("token")
return new Response
(
"Succeeded",
{
status:200
headers:
{
"Set-Cookie":`token:${token}`
}
}
)
p210 헤더다루기
export async function GET(req:Request)
headers = await headers()
referer = headers.get("referer")
return new Response
(
"Succeeded",
{
status:200
headers:
{
referer:referer
}
}
)
p211 URL 쿼리 파라미터
export async function GET(req:NextRequest)
searchParams = req.nextUrl.searchParams
offset = searchParams.get("offset")
p211 Request Body 데이타 읽기
export async function POST(req:NextRequest)
reqJson = req.json()
export async function POST(req:NextRequest)
title = req.formData().get("title")
p229 미들웨어 사용법
- /middleware.ts
export function middleware(req:NextRequest)
return NextResponse.redirect(new URL("/v2", req.url))
p242 클라이언트 컴포넌트에서 서버액션 사용
"use server"
export async function createPost()
await db.posts.create()
"use client"
export default WritePost()
return
<button onClick={createPost}>
게시글작성
</button>
p257 서버액션 사용하기
"use server"
export async function getPost() : Promise<MyPost[]>
{
import samplePosts from "@/lib/my_posts.json"
return new Promise(
resolve => {
setTimeout(()=>{
resolve(samplePosts)
}, 2_000)
}
)
}
"use server"
export async function getPost() : MyPost[]
{
import samplePosts from "@/lib/my_posts.json"
await Task.Delay(2_000)
return samplePosts
}
p277 SWR 사용하기
npm install swr
- lib/fetcher.ts
export default const fetcher = (...args) => fetch(...args).then(res => res.json())
export default function usePosts()
const { isLoading, data, error, mutate } = useSWR<MyPost[]>
(
"/api/posts"
fetcher
)
return
{
isLoading
posts:data
error
mutate
}
p313 cache 사용
import { cache } from "react"
import { MySvrUtil } from "@/lib/MySvrUtil"
const MySvrUtilCached = cache(MySvrUtil)
function MyComp():
const myValue = MySvrUtilCached.xxx(yyy)
p314 server-only 패키지
- 서버에서 작동하는 모듈을 클라이언트에서 사용하려고 하면 빌드오류 발생
npm install server-only
import "server-only"
export async function getData()
...