황현동 블로그 개발, 인생, 유우머

251126 책리뷰 소플 처음 만난 next.js

Tags:

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 용 폴더
  • 파일역할
    • eslint.config.mjs
      • eslint 설정파일
    • next-dev.d.ts
      • 타입설정
    • next.config.ts
      • 앱설정
    • tsconfig.json
      • 타입스크립트 설정
    • globals.css
    • layout.tsx
    • page.module.css
    • page.tsx
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

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

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 }

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()
    ...