[완] Next.js 15가지만 알면 끝남

Nanotoly·2024년 4월 16일
147
post-thumbnail
  • React 문법 모르면 보지 마세요.
  • 이걸로 공부할 생각하지 말고, next.js 공부한 후에 치팅시트용으로 쓰세요.
  • 글 우측 인덱스 잘 활용하시면 편할지도(?) ➡

1. 페이지 만들기

app/hello/page.js에 아래와 같은 코드를 넣으면
/hello 경로에서 페이지가 출력된다.

export default function Home() {
  return (
    <div>
      <h4 className="title">안녕하세요</h4>
    </div>
  )
}

2. 라우팅

라우팅은 <Link> 태그를 통해 구현 가능하다.

import Link from "next/link"; // <- 이거  넣기

export default function navbar() {
  return (
    <main>
      <div className="navbar">
        <Link href="/">home</Link> // <- 이렇게 원하는 링크 넣으면 
        <Link href="/list">list</Link>
      </div>
    </main>
  )
} 

다른 방법도 존재한다.

client component에서는 useRouter를 사용하여 js에서 페이지 이동이 가능하다.

'use client'

import {useRouter} from 'next/navigation'

export default function navbar(){
  let router = useRouter()
  return (
    <button onClick={()=>{ router.push('/') }}>home</button>
  )
}

useRouter에서는 라우팅과 관련된 다양한 기능이 있다.

<button onClick={()=>{ router.back() }}>버튼</button>
이는 뒤로가기 기능이다.

<button onClick={()=>{ router.forward() }}>버튼</button>
앞으로가기 기능이다.

<button onClick={()=>{ router.refresh() }}>버튼</button>
새로고침 기능이다. 일반적인 새로고침과는 다르게, 페이지 전체를 새로고침하는 것이 아니다. 이전과 다른점만 분석해서 새로고침하는 기능이다.
Next.js 공식문서에서는 soft refresh라고 말하는 기능이다.

<button onClick={()=>{ router.prefetch('/page') }}>버튼</button>
prefetch는 해당 url 경로를 미리 로드하는 기능이다. 미리 로드하여 해당 페이지로 이동할 때, 빠르게 이동할 수 있다.

현재 라우트 정보를 알고싶다면

client component에서 현재 url에 대한 정보를 알고 싶으면

'use client'

import {usePathname, useSearchParams, useParams} from 'next/navigation'

export default function DetailLink(){
  console.log(usePathname())
  console.log(useSearchParams())
  console.log(useParams())
}

usePathname()는 현재 url를 출력한다.
useSearchParams()는 쿼리 정보를 출력한다.
useParams()는 dynamic route에 입력된 파라미터를 출력한다.

3. 레이아웃

자세한 설명은 글로 어려우니 { children } 사용예제만 작성한다.
{ children } 안에는 page.js의 내용이 들어간다.

import Link from "next/link";
import './globals.css';

export default function RootLayout({ children }) { // <- 여기 children 넣기
  return (
    <html lang="en">
      <head />
      <body>
        <div className="navbar">
          <Link href="/">Home</Link>
          <Link href="/list">List</Link>
        </div>
        {children} // <- 원하는 곳에 children 넣기
      </body>
    </html>
  )
} 

4. 반복문

export default function List() {
let arr = ['first', 'second', 'third']
return (
  <div> 
    <h4>반복문 예시</h4>
    { 
      arr.map((item, index)=>{
        return ( 
          <div key={index}> // <- key  넣는거 중요!
            <h4>{ arr[index] }</h4>
          </div>
        )
    }) }
  </div>
)
}

5. 이미지 삽입

우선 public 폴더에 이미지를 넣어야 한다.

<img src="/image1.png" alt="이미지 설명"/>  

최적화된 이미지 넣는 법

성능과 속도를 위해서는 <Image/> 태그를 써야한다.
자동으로 이미지 lazy loading + 사이즈 최적화 + layout shift 방지를 해준다.

import Image from 'next/image' //<- import 해야함

export default function Home() {
  return(
    <div>
      <Image src={"/image1.png"} alt="이미지 설명"/>
    <div/>
)} 

6. server component / client component

server component

서버에서 랜더링해서 뿌려주는 컴포넌트다.

  • (장점) 페이지 로드가 빠르다.
  • (장점) SEO에 유리하다.(검색 했을 때 페이지 노출이 잘 된다.)
  • (단점) HMTL 안에 JS를 못 넣는다. useState, useEffect, onClick 등 사용 불가능하다.

client component

클라이언트가 직접 랜더링 해야하는 컴포넌트다.

  • (장점) HTML 안에 JS를 넣을 수 있다.
  • (단점) 페이지 용량도 커지고, 로딩 속도도 느려진다.
'use client' <- 이것만 추가하면 이 컴포넌트는 client component가 됨
import Image from 'next/image' //<- import 해야함

export default function Home() {
  return(
    <div>
      <Image src={"/image1.png"} alt="이미지 설명"/>
    <div/>
)} 

7. props

컴포넌트에 데이터를 전달해주려면 props를 사용하면 된다.

export default function ParentComponent() {
  let arr = ['first', 'second']
  return (
    <div>
      <h4>arr</h4>
      <ChildComponent abc={arr[0]}/>
      <ChildComponent abc={arr[1]}/>
    </div>
  )
}

function ChildComponent(props){
  return(
    <div>
      {props.abc}
    </div>
  )
}        

8. useState, useEffect, onClick

useState, useEffect, onClick... 이런것들을 이용하려면 use client를 사용해야한다.

아래는 가장 간단한 onClick 예시다.

'use client';

export default function HelloWorld(){
  return (
    <div onClick={()=>{ console.log("hello world") }}>버튼</div>
  )
}      

아래는 useState 예시다.

'use client';
import {useState} from 'react'

export default function Count() {
  let [count, setCount] = useState(0)
  return (
    <div>
      <span> {count} </span>
      <button onClick={()=>{ setCount(count+1) }}>+</button>
    </div> 
  )
} 

9. Array 자료형을 위한 setState

혹시라도 setState를 통해서 Array 자료형을 업데이트하려면 Deep copy에 대해서 알아야 한다.
아래 코드에서 onClick내에서 count[0]++; setCount(count);라고 작성했다면 state가 변경되지 않는다.

'use client';
import {useState} from 'react'

export default function Count() {
  let [count, setCount] = useState([0,0,0])
  return (
    <div>
      <span> {count[0]} </span>
      <button onClick={()=>{ 
        let copy = [...count]
        copy[0]++
        setCount(copy)
      }}>+</button>
      
      <span> {count[1]} </span>
      <button onClick={()=>{ 
        let copy = [...count]
        copy[1]++
        setCount(copy)
      }}>+</button>
      
      <span> {count[2]} </span>
      <button onClick={()=>{ 
        let copy = [...count]
        copy[2]++
        setCount(copy)
      }}>+</button>
    </div> 
  )
} 

10. Dynamic Route

/post/[postId]와 같이 유동적인 라우팅을 위해서는 아래와 같이 폴더를 구성하면 된다.

app
↳ post
   ↳[postId]
      page.js

page.js에서 postId 값을 조회하는 코드는 아래와 같다.

export default async function PostDetail(props) {
	console.log(props.params.postId)
    (생략)
}

11. API 만들기 / form을 통한 API 호출

Next.js에서는 백엔드까지 모두 구현이 가능하다.
/api/post 에 대응하는 API를 만드려면 아래와 같이 폴더 구성을 해야한다.

app
↳ ...

pages
↳ api
    post.js

post.js 내에 코드는 아래와 같이 구성한다.

export default function handler(req, res) {
  if (req.method == 'GET'){
    res.status(200).json({ result: 'GET 메소드 호출 완료' })
  }
  if (req.method == 'POST'){
  	console.log(req.body) // <- form에 작성된 내용은 req.body에 저장된다.
    res.status(200).json({ result: 'POST 메소드 호출 완료' })
  }
}

HTML 코드에서 이 API를 호출하는 코드는 아래와 같다.

export default function CallApi(){
  return (
    <div>
      <h4>글작성</h4>
      <form action="/api/post" method="POST">
      	<input name="title" placeholder="글제목"/>
        <input name="content" placeholder="글내용"/>
        <button type="submit">버튼</button>
      </form>
    </div>
  )
}

API에서도 URL parameter를 활용할 수 있다.
아래와 같이 폴더를 구성하면 된다.

app
↳ ...

pages
↳ api
   ↳ post
      [id].js

[id].js에서는 req.query로 parameter를 활용할 수 있다.

export default function handler(req, res) {
  console.log(req.query)
}

혹시 API 폴더 작업이 귀찮다면

server action이라는 문법이 있다.
그냥 page.js에서 <form>을 submit 했을 때 실행될 코드를 짤 수 있다.

이 방식을 쓰면 form 제출 후에 새로고침이 자동으로 안되니

  • 클라이언트 컴포넌트는 router.refresh()
  • 서버 컴포넌트는 revalidatePath('/path')를 써야한다.

Server component에서 쓰는 방법

import { connectDB } from "@/util/database";

export default async function Write() {
  async function handleSubmit(formData) {
    'use server';
    const db = (await connectDB).db('testDB')
    await db.collection('testCollection').insertOne({title : formData.get('title')})
    
    revalidatePath('/write') //<- 안쓰면 새로고침  
  }
 
 
  return (
    <form action={handleSubmit}> 
      <input type="text" name="title" />
      <button type="submit">Submit</button>
    </form>
  );
} 

Client component에서 쓰는 방법
page.js와 나란한 위치에 js 파일을 만들고

(example.js)

'use server'
 
export async function example() {
  폼 전송시 실행할 서버코드
} 

page.js에서는 이렇게 사용하면 된다.

'use client'
 
import { action1 } from './example'
 
export default function ClientComponent() {
  return (
    <form action={example}>
      <button type="submit">버튼</button>
    </form>
  )
} 

12. Ajax를 통한 API 호출

<form>을 통해서 API를 호출하게 되면 페이지가 새로고침이 된다.
새로고침되는 것을 원하지 않는다면 Ajax를 사용해야 한다.

코드는 아래와 같다.

<button onClick={()=>{
  fetch('/URL')
}}>버튼</button>

아래와 같이 활용 가능하다

<button onClick={()=>{
  fetch('/URL', { method : 'POST', body : 'hello world' })
}}>버튼</button>

API가 응답한 데이터로 무언가를 하려면

<button onClick={()=>{
  fetch('/URL').then(()=>{
    console.log('완료')
  })
}}>버튼</button>

API가 응답한 데이터가 Array 또는 Object라면

fetch('/URL')
.then((r)=>r.json())
.then((result)=>{ console.log(result) })

API에 Array나 Object를 전달하려면

<button onClick={()=>{
  fetch('/URL', { method : 'POST', body : JSON.stringify( {name : 'hello'} ) })
}}>버튼</button>

13. 성능개선

next.js 쓰는 이유 중 하나는 성능개선 때문이다. 페이지 방문자가 빠르게 로드되는 페이지를 경험할 수 있다.

13-1. Dynamic rendering / Static rendering

npm run build 를 실행하면 가끔 dynamic render이 되어야 하는 페이지가 static render가 되는 경우가 있다.
그런 페이지에는 아래와 같이 dynamic 변수를 설정하면 된다.

export const dynamic = 'force-dynamic' 

export default function 페이지(){
  (생략)
}

13-2. 캐싱

캐싱 기능을 통해 매번 서버에 데이터를 요청하지 않고, 이전에 불러온 데이터를 재사용할 수 있다.
캐싱 기능은 server component에서만 사용할 수 있다.

fetch('/URL', { cache: 'force-cache' }) 

캐싱 데이터를 60초만 보관하고 싶다면

fetch('/URL', { next: { revalidate: 60 } }) 

위 방법도 가능하고, 아래 방법도 가능함

(아무 page.js 파일)

export const revalidate = 60; // <- 이렇게 변수에 값만 넣어주면 됨

export default function Page() {
  (생략)
} 

캐싱 기능을 쓰지 않고, 매번 데이터를 불러오려면

fetch('/URL', { cache: 'no-store' }) 

14. loading.js, error.js

app
↳ post
   ↳[postId]
      page.js
      loading.js
      error.js

위와 같이 폴더를 구성하면 page.js가 로딩 시에는 loading.js의 내용이 출력된다.
에러 발생 시에는 error.js의 내용이 출력된다.

15. Middleware

app
↳ ...

middleware.js

app과 나란한 위치에 middleware.js를 만들면 된다.
여기에 작성된 코드는

  • 서버로 요청을 전달할 때
  • 페이지 접속할 때마다

실행이 된다.

아래와 같은 것들을 활용할 수 있다.

import { NextResponse } from 'next/server'

export async function middleware(request) {
  console.log(request.nextUrl)  //유저가 요청중인 URL 출력해줌
  console.log(request.cookies)  //유저가 보낸 쿠키 출력해줌
  console.log(request.headers)  //유저의 headers 정보 출력해줌 
  NextResponse.next()  //통과
  NextResponse.redirect()  //다른페이지 이동
  NextResponse.rewrite()  //다른페이지 이동
} 

15-1. 특정 페이지 접속 감시

/list에 사용자가 접속하는 경우 코드가 실행되도록 하려면

(/middleware.js)
import { NextResponse } from 'next/server'

export async function middleware(request) {
  if (request.nextUrl.pathname.startsWith('/list')) {
    console.log(new Date().toLocaleString()) // <- 현재 시각 출력됨
    console.log(request.headers.get('sec-ch-ua-platform')) // <- 클라이언트의 운영체제가 출력됨
    return NextResponse.next()
  }
} 

15-2. 페이지 권한 확인

로그인 된 사용자만 /write 페이지에 들어올 수 있다면

import { NextResponse } from 'next/server';
import { getToken } from "next-auth/jwt";

export async function middleware(request) {

  if (request.nextUrl.pathname.startsWith('/write')) {
    const session = await getToken({ req : request })
    console.log('세션', session)
    if (session == null) {
      return NextResponse.redirect(new URL('/api/auth/signin', request.url));
      // 또는 return NextResponse.redirect('http://localhost:3000/api/auth/signin');
    }
  }
} 

유용하게 활용되는 파트

1. mongoDB 사용방법

1-1. 설치

npm install mongodb

1-2. DB 불러오기

아무 경로에나 파일 만들고 아래 코드 넣기
예시에서는 /util/database.js에 두겠다.

import { MongoClient } from 'mongodb'
const url = 'mongodb+srv://...' <- 본인 mongoDB url 넣기
const options = { useNewUrlParser: true }
let connectDB

if (process.env.NODE_ENV === 'development') {
  if (!global._mongo) {
    global._mongo = new MongoClient(url, options).connect()
  }
  connectDB = global._mongo
} else {
  connectDB = new MongoClient(url, options).connect()
}
export { connectDB }

1-3. 데이터 불러오기

import { connectDB } from "/util/database.js"

export default async function Find() {
  let client = await connectDB;
  const db = client.db('DBname');
  let result = await db.collection('CollectionName').find().toArray();

  return (
    <main>
      {result[0].title}
    </main>
  )
}

1-4. 데이터 쓰기

import { connectDB } from "/util/database.js"

export default async function Insert() {
  const body = {...데이터...}
  
  let client = await connectDB;
  const db = client.db('DBname');
  let result = db.collection('CollectionName').insertOne(body)

  (생략)
}

2. next-auth 사용방법

2-1. 설치

npm install next-auth 으로 설치

2-2. [...nextauth].js 생성

pages/api/auth/[...nextauth].js 파일에 아래 코드 넣기

import NextAuth from "next-auth";
import GithubProvider from "next-auth/providers/github";

export const authOptions = {
  providers: [
    GithubProvider({
      clientId: 'Github에서 발급받은ID',
      clientSecret: 'Github에서 발급받은Secret',
    }),
  ],
  secret : process.env.NEXTAUTH_SECRET
};
export default NextAuth(authOptions); 

2-3. .env.local 생성

app
↳ ...

.env.local

.env.local 파일을 위와 같은 위치에 생성하고, 내용은 아래와 같이 한다.

NEXTAUTH_SECRET=JWT_생성에_쓰이는_암호

2-4. 로그인/로그아웃 기능

아래와 같이 간단하게 기능을 구현할 수 있다.

'use client';

import { signIn, signOut } from 'next-auth/react'

<button onClick={()=>{ signIn() }}>로그인</button>
<button onClick={()=>{ signOut() }}>로그아웃</button> 

2-5. 로그인된 유저 정보 활용

a. client component의 경우
<SessionProvider>라는걸 import 해와서 부모 컴포넌트를 감싸면 자식 컴포넌트들은 useSession() 이라는 함수를 이용가능하다

(page.js 옆에 있는 layout.js)

'use client'

import { SessionProvider } from "next-auth/react"

export default function Layout({ children }){
  return (
    <SessionProvider>
      {children}
    </SessionProvider>
  )
}
(page.js)

'use client'

import { useSession } from 'next-auth/react'
export default function Page(){
  let session  = useSession();
  if (session) {
    console.log(session)
  }
(생략)

b. server component의 경우

import { authOptions } from "@/pages/api/auth/[...nextauth].js"
import { getServerSession } from "next-auth"

export default function Page(){
  let session = await getServerSession(authOptions)
  if (session) {
    console.log(session)
  }

자주 실수하는 파트

1. onClick 내에 함수는 ()=>{}로 쓰기

만약에 onClick 내에 onClick={console.log("hello world)}를 입력하게 되면 페이지가 랜더링되면서 무조건 한번 실행된다. 클릭하기 전에 무조건 실행되니 아래와 같이 꼭 ()=>{} 안에 원하는 함수를 작성해야 한다.

'use client';

export default function HelloWorld(){
  return (
    <div onClick={()=>{ console.log("hello world") }}>버튼</div>
  )
}      

2. 서버와 데이터 주고 받을 때 .toString()

<form> 태그 내에 있는 <input> 태그를 통해 서버로 데이터를 전달하려는 경우

  • 문자
  • 숫자
  • JSON

만 전달이 가능하다.
이 외의 자료형이나 클래스의 경우에는 문자로 바꾸어 전달해야 한다.

<form action="/api/post/edit" method="POST">
    <input name="title" defaultValue={result.title}/>
    <input name="content" defaultValue={result.content}/>
    <input name="_id" defaultValue={result._id.toString()}/> //<--여기 toString() 참고할 것!
    <button type="submit">전송</button>
</form>

서버 측 코드에서도 전달받은 JSON을 parse해야만 전달받은 데이터를 활용할 수 있다.

export default async function handler(req, res){

    if(req.method == 'DELETE'){
        console.log(JSON.parse(요청.body).email)
    }
}
profile
항상 압도적이고 경외롭게🔥🔥🔥

6개의 댓글

comment-user-thumbnail
2024년 4월 20일

포스트 잘 보고 갑니다 !! 😊

1개의 답글
comment-user-thumbnail
2024년 4월 21일

server components에 대해 읽었는데 제가 알기로는 Next.js에서는 client components 또한 SEO 가 적용되는 것으로 알고 있는걸로 압니다. 그래서 실제로 Next.js 를 실행하고 브라우저상에서 js를 비활성화 해도 client components의 초기값은 이미 서버측에서 다 render하고 난 뒤라서 렌더링 되는 모습을 볼 수 있습니다! 이 부분 한번 확인해주시면 좋을 거 같아요!

1개의 답글
comment-user-thumbnail
2024년 4월 25일

Next.js 공부하고 있는데 정말 핵심적인 것들만 딱 잘 설명해주신 것 같아요..!! 잘 보고 가요~^^

답글 달기
comment-user-thumbnail
2024년 4월 25일

오 Next 풀스택으로 써보고 싶어지는 글이네요
잘 보고 갑니다~! 정리 👍🏻 👍🏻

답글 달기

관련 채용 정보