생활코딩의 NextJS 강의를 들으며 정리
React 서버 사이드 렌더링 프레임 워크
서버에서 자바스크립트를 로딩, 클라이언트 측에서 자바스크립트를 로딩하는 시간 단축
서버 측에서 html, css, 자바스크립트를 만들어 컨텐츠를 직접 업로드해 SEO 용이함
직관적인 페이지 기반 라우팅 시스템(동적경로 지원)
사전 렌더링, 정적 생성(SSG) 및 서버측 렌더링(SSR) 페이지별로 지원
더 빠른 페이지 로드를 위한 자동 코드 분할
최적화된 프리페치를 사용한 클라이언트측 라우팅
내장 CSS 및 Sass 지원, 모든 CSS-in-JS 라이브러리 지원
Fast Refresh를 지원하는 개발환경
Serverless Functions로 API 엔드포인트를 구축하기 위한 API 경로
확장 기능
npx create-next-app@latest .
현재 폴더에 next.js의 최신 버전을 설치하는 명령어
npm run dev
개발 모드에서 Next.js 애플리케이션 실행
src/app/layout.js
웹페이지의 기본적인 골격 구성
import './globals.css'
export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
{/* children은 src/app/page.js에서 return한 값 */}
</html>
)
}
npm run build
.next 폴더에 실서비스로 배포 가능한 애플리케이션 생성
npm run start
.next 폴더의 내용을 바탕으로 서비스 시작
(http://localhost:3000)
개발 버전을 그대로 실서비스로 제공하는 건 비효율적이고 보안성의 문제가 발생할 수 있음
build
를 통해 용량을 줄이고 불필요한 메세지를 출력하지 않는 등 실서비스에 최적화된 환경 제공
layout
공통된 내용(페이지 등의 이동으로 변하지 않는 부분) 관리
import './globals.css'
export const metadata = {
title: 'WEB tutorial', // 사이트의 타이틀
description: 'Generated by egoing',
}
export default function RootLayout({ children }) {
return (
<html>
<body>
<h1><a href="/">WEB</a></h1>
<ol>
<li><a href="/read/1">html</a></li>
<li><a href="/read/2">css</a></li>
</ol>
{children}
<ul>
<li><a href="/create">create</a></li>
<li><a href="/update/id">update</a></li>
<li><button>delete</button></li>
</ul>
</body>
</html>
)
}
사용자가 접속한 URL의 path에 따라 콘텐츠를 응답해주는 작업
http://a.com/board/topics/
a.com
= domain
/board
= segment
/topics
= segment
(/board/topics/
= path)
/app
폴더 밑에 segment의 이름(ex.borad)으로 폴더 생성
/app/board/page.js
파일 생성
export defualt function Board(){
return (
<>Board</>
)
}
app/board/page.js
는 /board
경로를 의미
만약 board/layout.js
가 있다면, board/page.js
의 내용은 board/layout.js
와 결합하여 표시(이 layout.js
는 부모 폴더의 layout.js
에 안기는 형태가 됨)
없다면 그 부모 폴더(/app
)의 layout.js
와 결합
게시글처럼 id값 등에 의해 가변적으로 변경되는 경로를 의미
ex) read/1
, read/2
app/read/[id]
라는 폴더 경로 안에 page.js
파일 생성
// app/read/[id]/page.js
export default function Read(props){
return (
<>{props.params.id}</>
)
}
하나의 페이지에서 모든 작업을 처리하는 앱
서버로부터 데이터를 가져올 때는 ajax와 같은 방법을 사용, 동적으로 로딩
a
태그 대신 Link
사용
이동 버튼을 누르기 전에, 백그라운드에 미리 해당 페이지를 다운
export default function RootLayout({ children }) {
return (
<html>
<body>
<h1><Link href="/">WEB</Link></h1>
...
</body>
</html>
)
}
이미지, robots.txt, favicon.ico 와 같은 파일 : static assets
/public
폴더에 이미지와 같은 파일을 위치
ex) /public/hello.png
=> <img src='/hello.png' />
방법으로 사용
npx json-server --port 9999 --watch db.json
React 18ver 부터 서버 컴포넌트와 클라이언트 컴포넌트가 구분
Server Component : secure data / cookie / header ...
(fetch)
Client Component : useState / useEffect / onClick / onChange / useRouter / useParams ...
서버 컴포넌트
정보를 단순히 보여주는 역할을 하는 컴포넌트(한 번 렌더링 되면 클라이언트로 보내주는 역할)
사용자와 상호작용하지 않는 경우
백엔드에 엑세스하면서 보안적으로 위험한 정보를 주고 받는 경우
클라이언트 컴포넌트
사용자와 상호작용하는 경우
서버 컴포넌트로 해결되지 않는 경우
useEffect, useState, onClick, onChage와 같은 API를 사용해야 하는 경우
useRouter, useParams와 같은 NextJs의 client component API를 사용하는 경우
nextjs는 컴포넌트를 기본적으로 서버 컴포넌트로 간주
최상단에 "use client";
를 사용해 클라이언트 컴포넌트로 변환
export default async function RootLayout({ children }) {
const resp = await fetch('http://localhost:9999/topics/')
const topics = await resp.json();
return (
<html>
...
<ol>
{topics.map(topic=>{
return <li key={topic.id}><Link href={`/read/${topic.id}`}>{topic.title}</Link></li>
})}
</ol>
...
</html>
)
}
서버 컴포넌트는 모든 작업을 서버쪽에서 처리, 결과만 클라이언트로 전송(서버사이드 렌더링)
app/read/[id]/page.js
export default async function Read(props){
const id = props.params.id;
const resp = await fetch(`http://localhost:9999/topics/${id}`);
const topic = await resp.json();
return <>
<h2>{topic.title}</h2>
{topic.body}
</>
}
'use client'
import { useRouter } from "next/navigation"; // 'next/router'은 이전버전
export default function Create(){
const router = useRouter();
return <form onSubmit={async e=>{
e.preventDefault();
const title = e.target.title.value;
const body = e.target.body.value;
const resp = await fetch('http://localhost:9999/topics/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({title, body})
});
const topic = await resp.json();
router.push(`/read/${topic.id}`); // 방금 입력한 게시글로 이동
router.refresh(); // 서버 컴포넌트를 다시 렌더링(새로고침)
}}>
<h2>Create</h2>
<p><input type="text" name="title" placeholder="title" /></p>
<p><textarea name="body" placeholder="body"></textarea></p>
<p><input type="submit" value="create" /></p>
</form>
}
{cache:'no-cache'}
를 사용하면 캐쉬를 사용하지 않게 됨const resp = await fetch('http://localhost:9999/topics/', {cache:'no-cache'})
app/layout
에서는 app/read/[id]
의 id값을 알 수 없기 때문에, useParams가 필요
useParams는 client component 훅이기 때문에, server component에서 client component 훅이 필요한 부분만 따로 컴포넌트 화하여 사용
(가능한 서버 컴포넌트를 사용, 그러지 못하는 경우에만 클라이언트 컴포넌트를 사용하는게 좋음)
수정(update) = read + create
'use client';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
export default function Update(props) {
const router = useRouter();
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const id = props.params.id;
async function refresh() {
const resp = await fetch(`http://localhost:9999/topics/${id}`);
const topic = await resp.json();
setTitle(topic.title);
setBody(topic.body);
}
async function update(e) {
e.preventDefault();
const title = e.target.title.value;
const body = e.target.body.value;
const resp = await fetch(`http://localhost:9999/topics/${id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ title, body }),
});
const topic = await resp.json();
router.push(`/read/${topic.id}`);
router.refresh();
}
useEffect(() => {
refresh();
}, []);
// client component
return (
<form
onSubmit={update}
>
...
</form>
);
}
'use client';
import Link from 'next/link';
import { useParams, useRouter } from 'next/navigation';
export function Control() {
...
async function deletePage() {
const resp = await fetch(`http://localhost:9999/topics/${id}`, {
method: 'DELETE',
});
await resp.json();
router.push('/');
router.refresh();
}
return (
...
);
}
코드에 포함시킬 수 없는 정보는 환경변수로 관리, 보안을 위한 장치
.env.local
을 홈 디렉터리에 생성, 해당 파일에 변수 설정
변수는 API_URL=http://localhost:9999/
방식으로 저장
컴포넌트에서 사용할 때에는 process.env.API_URL
의 형식으로 사용
ex) const resp = await fetch(
{id})
NEXT_PUBLIC_
접두사 필요nextjs는 .env.local은 버전관리 할 필요가 없도록 기본 설정
샘플 정보를 담을 때는 .env.local.example
형식으로 사용(공개용)