app/hello/page.js에 아래와 같은 코드를 넣으면
/hello 경로에서 페이지가 출력된다.
export default function Home() {
return (
<div>
<h4 className="title">안녕하세요</h4>
</div>
)
}
라우팅은 <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에 입력된 파라미터를 출력한다.
자세한 설명은 글로 어려우니 { 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>
)
}
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>
)
}
우선 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/>
)}
서버에서 랜더링해서 뿌려주는 컴포넌트다.
클라이언트가 직접 랜더링 해야하는 컴포넌트다.
'use client' <- 이것만 추가하면 이 컴포넌트는 client component가 됨
import Image from 'next/image' //<- import 해야함
export default function Home() {
return(
<div>
<Image src={"/image1.png"} alt="이미지 설명"/>
<div/>
)}
컴포넌트에 데이터를 전달해주려면 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>
)
}
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>
)
}
혹시라도 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>
)
}
/post/[postId]
와 같이 유동적인 라우팅을 위해서는 아래와 같이 폴더를 구성하면 된다.
app
↳ post
↳[postId]
page.js
page.js
에서 postId 값을 조회하는 코드는 아래와 같다.
export default async function PostDetail(props) {
console.log(props.params.postId)
(생략)
}
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)
}
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>
)
}
<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>
next.js 쓰는 이유 중 하나는 성능개선 때문이다. 페이지 방문자가 빠르게 로드되는 페이지를 경험할 수 있다.
npm run build
를 실행하면 가끔 dynamic render이 되어야 하는 페이지가 static render가 되는 경우가 있다.
그런 페이지에는 아래와 같이 dynamic 변수를 설정하면 된다.
export const dynamic = 'force-dynamic'
export default function 페이지(){
(생략)
}
캐싱 기능을 통해 매번 서버에 데이터를 요청하지 않고, 이전에 불러온 데이터를 재사용할 수 있다.
캐싱 기능은 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' })
app
↳ post
↳[postId]
page.js
loading.js
error.js
위와 같이 폴더를 구성하면 page.js가 로딩 시에는 loading.js의 내용이 출력된다.
에러 발생 시에는 error.js의 내용이 출력된다.
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() //다른페이지 이동
}
/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()
}
}
로그인 된 사용자만 /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');
}
}
}
npm install mongodb
아무 경로에나 파일 만들고 아래 코드 넣기
예시에서는 /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 }
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>
)
}
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)
(생략)
}
npm install next-auth
으로 설치
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);
app
↳ ...
.env.local
.env.local
파일을 위와 같은 위치에 생성하고, 내용은 아래와 같이 한다.
NEXTAUTH_SECRET=JWT_생성에_쓰이는_암호
아래와 같이 간단하게 기능을 구현할 수 있다.
'use client';
import { signIn, signOut } from 'next-auth/react'
<button onClick={()=>{ signIn() }}>로그인</button>
<button onClick={()=>{ signOut() }}>로그아웃</button>
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)
}
만약에 onClick 내에 onClick={console.log("hello world)}
를 입력하게 되면 페이지가 랜더링되면서 무조건 한번 실행된다. 클릭하기 전에 무조건 실행되니 아래와 같이 꼭 ()=>{} 안에 원하는 함수를 작성해야 한다.
'use client';
export default function HelloWorld(){
return (
<div onClick={()=>{ console.log("hello world") }}>버튼</div>
)
}
<form>
태그 내에 있는 <input>
태그를 통해 서버로 데이터를 전달하려는 경우
만 전달이 가능하다.
이 외의 자료형이나 클래스의 경우에는 문자로 바꾸어 전달해야 한다.
<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)
}
}
포스트 잘 보고 갑니다 !! 😊