한 걸음씩
Next.js 13 기초 본문
CSR(Client-Side Rendering) : JavaScript, React |
장점 |
|
단점 |
|
|
SSR(Server-Side Rendering) : Next.js |
장점 |
|
단점 |
|
▶ Next.js 설치, 실행
npx create-next-app@latest .
// 폴더를 하나 만들고 vscode로 열어 현재 위치(.)에 next.js 설치
npm run dev
개발 중인 경우 위의 명령어를 사용한다. 이는 개발 서버를 실행하고 코드 변경을 감지하여 빠르게 반영하는 기능을 제공한다. 개발 중에는 코드 수정과 디버깅을 수행하며, 자동으로 변경 사항을 확인하고 테스트할 수 있다.
▶ 샘플 앱 세탁
// 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>
</html>
)
}
// page.js
import Image from 'next/image'
export default function Home() {
return <>Hello, Next.js</>
}
// globals.css
// 기본 css 설정 모두 삭제
▶ 빌드와 배포
npm run build
이 명령어는 일반적으로 프론트엔드 프로젝트에서 사용된다. 주로 웹 애플리케이션의 소수 코드를 번들링하고 최적화하여 배포 가능한 형태로 변환한다.
1. 소스 코드 번들링: 프로젝트 내의 JS, CSS, 이미지 등의 파일을 하나로 묶어주는 작업을 수행한다. 이렇게 번들링된 파일은 웹 애플리케이션의 성능과 로딩 속도를 개선하는 데 도움이 된다.
2. 최적화: 번들링된 파일을 압축하고 불필요한 코드나 리소스를 제거하여 파일 크기를 최소화한다. 이는 사용자가 애플리케이션을 더 빠르게 로드할 수 있도록 도와준다.
3. 환경 설정: 빌드 환경에 따라 환경 변수나 설정을 적용하여, 개발 환경과 프로덕션 환경을 분리하여 관리할 수 있다.
npm run start
이 명령어는 개발 서버를 실행하는 데 사용된다. 개발 서버는 애플리케이션을 로컬 환경에서 실행하여 개발 중인 코드를 실시간으로 확인하고 테스트할 수 있는 환경을 제공한다.
애플리케이션을 실제 운영 환경에 배포하거나 실행할 때는 위의 명령어를 사용하는 것이 일반적이다. 이는 프로덕션용으로 빌드된 코드를 실행하는 역할을 한다. 프로덕션 환경에서는 주로 성능 및 보안에 더 중점을 두어야 하므로, 개발 환경과는 조금 다른 설정을 사용하는 경우가 많다.
1. 로컬 서버 실행: 개발 중인 웹 애플리케이션을 로컬 환경에서 실행한다. 이를 통해 코드 수정 사항을 실시간으로 확인하고 디버깅할 수 있다.
2. 핫 리로딩(Hot Reloading): 코드를 수정할 때마다 브라우저를 새로고침하지 않아도 변경 내용이 반영되는 기능을 제공한다. 이로써 빠른 개발 속도를 유지할 수 있다.
3. 디버깅: 개발 서버는 디버깅 도구와 연계하여 오류를 식별하고 수정하는 데 도움을 준다.
메모리 변화: 두 명령어 모두 웹 개발 및 애플리케이션 실행에 필요한 작업을 수행하므로, 메모리 변화가 생길 수 있다.
이러한 변화는 일반적으로 정상적인 동작이며, 성능 개선 및 개발 편의성을 위한 과정이다.
▶ 뼈대 만들기 - layout & page
// page.js
import Image from 'next/image'
export default function Home() {
return (
<>
<h2>Welcome</h2>
Hello, Next.js
</>
)
}
// layout.js
import './globals.css'
import Link from 'next/link'
// 웹사이트의 타이틀
export const metadata = {
title: 'Web tutorial',
description: 'Generated by winter',
}
// 공통으로 표시할 컴포넌트
export default function RootLayout({ children }) {
return (
<html>
<body>
<h1>
<Link href="/">WEB</Link>
</h1>
<ol>
<li>
<Link href="/read/1">html</Link>
</li>
<li>
<Link href="/read/2">css</Link>
</li>
</ol>
{/* children prop: page.js를 표시하는 부분 */}
{children}
<ul>
<li>
<Link href="/create">Create</Link>
</li>
<li>
<Link href="/update/1">Update</Link>
</li>
<li>
<input type="button" value="delete"></input>
</li>
</ul>
</body>
</html>
)
}
▶ Next.js의 도로교통 시스템 - Routing
◇ layout.js
- 공통된 레이아웃을 사용하여 여러 페이지에서 반복되는 요소를 효율적으로 관리하고 유지보수할 수 있다.
- 헤더, 푸터, 내비게이션 바 등과 같은 공통 컴포넌트를 정의할 수 있다.
- 각 페이지의 내용은 이 레이아웃에 포함된 요소들과 결합되어 전체 페이지가 렌더링 된다.
- 공통된 레이아웃 컴포넌트
◇ page.js
- 실제 각 페이지의 내용을 작성하고 layout.js와 조합되어 최종적인 페이지가 생성된다.
- 특정 페이지 컴포넌트
// create/layout.js
export default function Layout(props) {
return (
<form>
<h2>Create</h2>
{props.children}
</form>
)
}
// create/page.js
export default function Create() {
return <>Create!</>
}
// read/[id]/page.js
export default function Read(props) {
return (
<>
<h2>Read</h2>
{/* layout.js에서 Link컴포넌트 경로 지정할 때 사용한 id를 가져옴 */}
parameters: {props.params.id}
</>
)
}
▶ Single Page Application(SPA)
- 전통적인 다중 페이지 애플리케이션(Multi Page Application, MPA)과 대조되는 개념
- SPA는 한 개의 HTML 페이지로 시작하고, 사용자와의 상호작용에 따라 필요한 데이터를 동적으로 로드하여 페이지를 갱신하며 전체 애플리케이션을 구성한다.
◇ SPA 특징
- 단일 페이지: 모든 페이지 요청에 대해 한 개의 HTML 페이지를 먼저 로드하고, 필요한 데이터나 컴포넌트 등을 동적으로 변경하면서 페이지를 업데이트한다.
- 빠른 전환: 페이지 갱신이 필요한 경우에도 새로운 페이지를 서버로부터 가져오는 대신 필요한 데이터만 가져와 렌더링 하므로 전체 페이지 갱신보다 빠른 사용자 경험을 제공할 수 있다.
- 클라이언트 측 라우팅: 페이지 간의 이동을 클라이언트 측에서 처리하여 필요한 부분만 업데이트한다. 이를 위해 라우팅 라이브러리(React Router, Vue Router)를 사용한다.
- API 통신: 데이터를 비동기적으로 서버에서 가져와 렌더링 하므로 API 호출이 중요한 역할을 한다.
- 재사용 가능한 컴포넌트: 컴포넌트 기반 구조를 활용하여 UI 요소를 재사용하고 관리한다.
◇ SPA에서 Link 태그를 사용하는 이유
- 클라이언트 측 라우팅: Link 태그는 클라이언트 측 라우팅을 지원하는 라이브러리와 함께 사용할 수 있어, 페이지 간의 전환을 부드럽게 처리할 수 있다.
- 페이지 전환 없음: Link 태그를 사용하면 페이지가 전환되더라도 브라우저가 새로고침하지 않고 애플리케이션의 상태를 유지한다. 이는 사용자 경험을 향상하고, 빠른 페이지 로딩을 유지할 수 있는 장점을 가진다.
- SEO: Link 태그와 클라이언트 측 라우팅을 사용하면 검색 엔진 최적화(SEO)에 유리한 환경을 구성할 수 있다. 검색 엔진은 클라이언트 측 라우팅을 지원하는 방식으로 콘텐츠를 수집하고 인덱싱하는 추세이다.
- URL 변경: Link 태그와 클라이언트 측 라우팅을 사용하면 URL을 변경하여 브라우저 히스토리를 관리할 수 있다. 이로써 사용자가 뒤로 가기, 앞으로 가기 등의 브라우저 기능을 사용할 때도 일관된 경험을 제공할 수 있다.
▶ 정적인 자원 사용하기 - public
- public 폴더는 정적인 자원(이미지, CSS파일, JavaScript 파일 등)을 저장하는 폴더
- 이 폴더 내의 자원은 빌드 과정에서 수정되지 않고 그대로 복사되어 최종 빌드된 애플리케이션의 루트 경로에 위치하게 된다.
- SPA에서 절대 경로를 사용하는 것이 더 간편하며 일반적으로 권장되는 방식이다.
- 절대 경로를 사용하면 이미지의 위치를 변경해도 링크를 수정하지 않아도 되기 때문이다.
- 상대 경로를 사용하려면 경로 계산이 조금 더 복잡해질 수 있다.
- 상대 경로 (Relative Path)
- 상대 경로를 사용할 때는 현재 파일을 기준으로 이미지의 경로를 지정한다.
- 만약 'page.js'에서 이미지를 가져오려면 public 폴더 아래의 경로를 상대 경로로 표현해야 한다.
- 예를 들어, 'page.js' 파일이 src폴더에 위치하고, 이미지 파일이 public 폴더에 있을 경우 'public/hello.png' 경로는 애플리케이션 루트 기준으로 이미지를 찾으려고 하므로 이미지가 로드되지 않는다.
- 절대 경로 (Absolute Path)
- 절대 경로를 사용할 때는 이미지의 경로를 애플리케이션의 루트부터 지정한다.
- '/public' 폴더까지의 경로를 생략하고 이미지의 파일명만 지정한다. '/hello.png' ← 이 경우 이미지가 정적인 자원으로서 빌드 과정에서 루트 경로에 복사되므로 정상적으로 이미지가 로드될 것이다.
// src/page.js
import Image from 'next/image'
export default function Home() {
return (
<>
<h2>Welcome</h2>
Hello, Next.js
<br />
{/* public 폴더에 있는 hello 이미지를 가리킴 */}
<img src="/hello.jpg" style={{ width: 100 }}></img>
</>
)
}
▶ css
// src/layout.js
import './globals.css'
// 전역적으로 스타일을 적용
// globals.css
body {
color: green;
background-color: pink;
}
▶ backend
- 데이터를 임시로 테스트용으로 작성해서 사용하는 것은 일반적인 접근 방법 중 하나이다.
- 개발 중에는 실제 백엔드 서비스와 연동되지 않은 상태에서도 프론트엔드를 개발하고 테스트하는 데 도움이 된다.
- 이렇게 하면 프론트엔드와 백엔드를 병렬로 개발하면서 빠르게 피드백을 받을 수 있다.
- 실제 운영 환경에서는 백엔드 서비스와 연동하여 데이터를 가져오게 될 것이며, 개발 중에 임시 데이터를 사용하는 것은 주로 개발 속도를 높이기 위한 목적으로 활용된다.
npx json-server --port 9999 --watch db.json
JSON 형식의 가상 REST API 서버를 실행하는 것이다. 이 명령어를 사용하면 테스트 및 개발 목적으로 간단한 API 서버를 만들 수 있다.
- npx: npm 패키지를 실행하고 사용할 수 있는 도구이다. 해당 패키지가 설치되어 있지 않아도 일시적으로 실행할 수 있게 해주는 역할을 한다.
- json-server: JSON 데이터를 사용하여 REATful API 서버를 만들어주는 패키지이다. 이를 통해 JSON 파일을 데이터베이스처럼 다룰 수 있다.
- --port 9999: 서버를 실행할 포트 번호를 지정한다. 위의 명령어에서는 포트 번호로 9999를 사용하도록 지정한 것이다.
- --watch db.json: db.json 파일을 모니터링하며, 해당 파일의 변경 내용을 자동으로 감지하여 서버를 업데이트한다. db.json 파일은 데이터를 저장하는 데 사용되며, 이 파일이 변경되면 서버의 데이터도 변경된다.
- 실행된 서버는 'http://localhost:9999' 주소로 접속하여 JSON 데이터를 조회하거나 조작할 수 있다. db.json 파일에는 가상의 데이터가 저장되어 있고, 이를 통해 API 엔드포인트를 테스트하고 개발할 수 있다. 이렇게 생성된 API 서버는 간단한 프로토타이핑이나 테스트 환경 구성에 유용하게 사용될 수 있다.
// db.json
// topics 객체 추가
{
"topics": [
{ "id": 1, "title": "html", "body": "html is ..." },
{ "id": 2, "title": "css", "body": "css is ..." }
],
...
}
'http://localhost:9999/topics' 주소로 접속 ↓
◇ 'db.json'에 작성된 데이터 가져오기
// 주어진 URL에 GET 요청을 보내는 역할, 해당 API 엔드포인트의 데이터를 가져오고자 한다.
fetch('http://localhost:9999/topics')
// 서버로부터 받은 응답(reponse)객체의 json()메서드를 호출하여 응답을 JSON 형식으로 변환한다.
// 이후 then 메서드로 넘겨줄 값을 반환한다.
.then(response => {return response.json()})
// 이전 단계에서 JSON으로 변환된 데이터(result)를 받아와서 콘솔에 출력한다.
.then(result => {console.log('result:', result)})
fetch 함수를 사용하여 API 엔드포인트인 'http://localhost:9999/topics'로 요청을 보내고, 서버에서 응답한 JSON 데이터를 가져오는 역할을 한다.
▶ 글 목록 가져오기
// src/layout.js
import { useEffect, useState } from 'react'
export const metadata = {
title: 'Web tutorial',
description: 'Generated by winter',
}
export default function RootLayout({ children }) {
const [topics, setTopics] = useState([])
useEffect(() => {
fetch('http://localhost:9999/topics')
.then(response => response.json())
.then(result => {
setTopics(result)
})
})
useEffect, useState는 Client Component이기 때문에 사용할 수 없다는 에러 메시지가 발생하는데
이를 해결하려면 상단에 'use client'라고 명시해줘야 한다. 그렇게 하면 'layout.js'는 클라이언트 컴포넌트가 된다.
그런데 metadata는 서버 컴포넌트이기 때문에 에러가 발생한다. 우선 주석처리하고 확인하면 정상적으로 페이지가 출력된다.
// src/layout.js
'use client'
import { useEffect, useState } from 'react'
// export const metadata = {
// title: 'Web tutorial',
// description: 'Generated by winter',
// }
export default function RootLayout({ children }) {
const [topics, setTopics] = useState([])
useEffect(() => {
fetch('http://localhost:9999/topics')
.then(response => response.json())
.then(result => {
setTopics(result)
})
})
위처럼 useEffect를 사용하여 데이터를 응답받아 페이지를 로드시키면 클라이언트 측에서 컴포넌트가 렌더링 된 이후에 데이터를 가져온다. useEffect는 컴포넌트가 화면에 렌더링된 후에 실행되므로, 초기 렌더링 시에는 받아오는 데이터가 나타나지 않는다. 사용자의 브라우저에서 JavaScript가 실행되기 때문에 컴포넌트의 인터랙션에 즉각 반응하면서 데이터를 가져오게 된다.
클라이언트 측에서 렌더링 시킨다면 초기 렌더링 시에 html, css 링크가 나타나지 않는 반면,
서버 측에서 렌더링 시킨다면 초기 렌더링 시에도 html, css 링크가 나타난다.
// src/layout.js
// SSR
export const metadata = {
title: 'Web tutorial',
description: 'Generated by winter',
}
export default async function RootLayout({ children }) {
const response = await fetch('http://localhost:9999/topics')
const topics = await response.json()
서버 사이드 렌더링(SSR)을 활용하여 서버에서 데이터를 미리 가져와서 페이지를 렌더링 할 수 있다.
이로써 초기 렌더링 시에 필요한 데이터를 페이지가 로드되기 전에 가져올 수 있어 사용자 경험을 향상할 수 있다.
▶ 읽기 기능 구현
// read/[id]/pages.js
export default async function Read(props) {
const response = await fetch(
`http://localhost:9999/topics/${props.params.id}`
)
const json = await response.json()
return (
<>
<h2>{json.title}</h2>
{json.body}
</>
)
}
▶ 생성 기능 구현 - Client component
// create/page.js
'use client'
import { useRouter } from 'next/navigation' // 페이지 간의 네비게이션 처리
export default function Create() {
const router = useRouter()
return (
// onSubmit은 client에서 작동하기 때문에 'use client' 명시해야 함
// 폼이 제출되면 서버에 새 글을 생성하는 API호출을 실행하고,
// 생성된 글의 ID를 받아 해당 글을 읽는 페이지로 이동한다.
<form
onSubmit={e => {
e.preventDefault() // 새로고침 방지
// 사용자가 입력한 제목(title)과 내용(value)을 가져온다.
const title = e.target.title.value
const body = e.target.body.value
// options: 글을 생성하는 API호출에 필요한 설정 객체
const options = {
// HTTP 요청 메서드 중 하나인 POST메서드를 사용하여 서버에 데이터를 전송한다.
// POST메서드는 클라이언트에서 서버로 데이터를 전송하고, 그 데이터를 서버에서 처리하도록 요청하는 역할을 한다.
method: 'POST',
// HTTP요청 또는 응답의 헤더 정보를 설정하는 객체이다.
// 헤더는 요청이나 응답의 추가적인 메타데이터를 포함하는 부분
headers: {
// 요청 본문의 데이터 형식이 JSON 형식임을 명시하고 있따.
// 서버에 JSON 형식의 데이터를 보내기 위한 설정이다.
'Content-Type': 'application/json',
},
// HTTP요청의 본문에 전송할 데이터를 JSON 형식으로 변환하여 설정한다.
// 이 데이터는 실제로 서버로 전송되는 데이터로, 서버에서 해당 데이터를 읽어 처리할 수 있다.
body: JSON.stringify({ title, body }), // JSON형식의 문자열로 변환하여 서버로 전송된다.
}
// Fetch API를 사용하여 서버에 POST 요청을 보낸다.
// options객체를 전달하는 이유는 요청의 세부적인 설정을 제어하고 정의하기 위해서이다.
fetch(`http://localhost:9999/topics`, options)
.then(res => res.json()) // 서버의 응답을 JSON형식으로 변환한다.
// 응답 데이터를 처리하고, 생성된 글의 ID를 가져와서 페이지를 해당 글의 읽기 페이지로 이동시킨다.
.then(result => {
console.log(result)
const lastId = result.id
// 지정한 경로로 페이지를 이동시킨다. 새로 생성된 글의 ID를 이용하여 해당 글의 읽기 페이지로 이동한다.
router.push(`read/${lastId}`)
})
}}
>
<p>
<input type="text" name="title" placeholder="title"></input>
</p>
<p>
<textarea name="body" placeholder="body"></textarea>
</p>
<p>
<input type="submit" value="create"></input>
</p>
</form>
)
}
db.json 파일을 확인해 보면 생성한 글의 데이터를 볼 수 있다.
// db.json
"topics": [
{
"id": 1,
"title": "html",
"body": "html is ..."
},
{
"id": 2,
"title": "css",
"body": "css is ..."
},
{
"title": "javascript",
"body": "javascript is...",
"id": 3
},
{
"title": "react",
"body": "react is ...",
"id": 4
},
{
"title": "next.js",
"body": "next.js is ...",
"id": 5
}
],
▶ cache
- cache: 데이터나 값을 미리 저장하여 나중에 더 빠르게 액세스 하거나 재사용할 수 있도록 하는 메모리 또는 저장 공간
- 캐시는 데이터를 저장하고 검색하는 속도를 높이고, 시스템 성능을 향상하는 데 사용된다.
- 목적: 성능 향상, 데이터 보관, 네트워크 부하 감소, 복잡한 계산 감소
// src/layout.js
const response = await fetch('http://localhost:9999/topics', {
cache: 'no-store',
})
'cache', 'no-store''는 웹 브라우저나 HTTP 클라이언트가 데이터를 저장하고 재사용하는 방식을 조절하는 용어
이들은 HTTP 응답 헤더에서 사용되며, 웹 애플리케이션에서 서버로부터 데이터를 가져오거나 보낼 때 캐싱 동작을 제어하기 위해 사용된다.
- cache: 캐싱 동작을 설정하는 속성이다. 서버에서 전달된 데이터를 클라이언트(브라우저)측에 저장하여 나중에 동일한 요청이 있을 때 다시 서버에 요청하지 않고 저장된 데이터를 사용할 수 있도록 한다.
- no-store: 데이터를 캐시 하지 않도록 지시하지 않는 속성이다. 서버에서 받은 데이터를 브라우저 캐시에 저장하지 않고, 매번 새로운 요청으로 서버에 데이터를 요청하게 된다.
- 'cache: no-store': 서버로부터 받은 데이터를 브라우저의 캐시에 저장하지 않고, 매번 새로운 요청으로 서버에서 데이터를 가져오게 된다. 이는 데이터가 항상 최신 상태로 유지되어야 하는 경우나, 프런트엔드 애플리케이션이 항상 실시간 데이터를 보여주어야 하는 경우에 유용하다.
// create/page.js
fetch(`http://localhost:9999/topics`, options)
.then(res => res.json())
.then(result => {
console.log(result)
const lastId = result.id
router.refresh()
router.push(`read/${lastId}`)
})
'router.refresh()'는 새로운 글이 생성된 후에 페이지를 갱신하고 새로운 글의 상세 페이지로 이동하기 위해 사용되었다. 이전에 생성한 글 목록 페이지에서 새로운 글을 생성하면, 생성된 글의 ID를 얻어와서 해당 글의 상세 페이지로 이동하기 위해 router.push()를 사용 한다. 그런데 새로운 글이 생성된 후에 페이지가 새로 렌더링 되지 않고 이동할 경우, 새로운 글이 목록에 표시되지 않을 수 있다. 따라서 'router.refresh()'를 사용하여 페이지를 다시 렌더링 하고 그 후에 'router.push()'로 상세 페이지로 이동하는 것이다.
'router.refresh()'이 없다면 생성 페이지로 이동은 하지만 목록에 나타나지는 않고 새로고침을 해야 나타난다.
https://nextjs.org/docs/app/building-your-application/caching
Building Your Application: Caching | Next.js
Next.js improves your application's performance and reduces costs by caching rendering work and data requests. This page provides an in-depth look at Next.js caching mechanisms, the APIs you can use to configure them, and how they interact with each other.
nextjs.org
△ cache에 대해서 반드시 자세히 공부하기!
▶ update, delete 버튼 구현
// src/layout.js
import Control from './Control'
export default async function RootLayout({ children }) {
...
return(
<Control />
)
...
}
create, update, delete 버튼을 나타내는 JSX코드를 삭제한 후 Control 컴포넌트를 생성하여 import 한다.
// src/Control.js
'use client'
import Link from 'next/link'
import { useParams } from 'next/navigation'
export default function Control() {
const params = useParams()
const id = params.id
return (
<ul>
<li>
<Link href="/create">Create</Link>
</li>
{id ? (
<>
<li>
<Link href={`/update/${id}`}>Update</Link>
</li>
<li>
<input type="button" value="delete" />
</li>
</>
) : null}
</ul>
)
}
Control 컴포넌트를 생성한 이유는 layout.js에서 id값을 가져오려면 useParams를 사용해야 하는데 useParams는 클라이언트에서만 사용가능 하므로 Control 컴포넌트를 따로 생성하여 useParams를 사용한다.
id의 존재여부와 상관없이 create버튼은 공통으로 생성하고
id가 존재하는 경우에만 update, delete버튼을 구현한다.
▶ 수정 기능 구현
// update/page.js
'use client'
import { useParams, useRouter } from 'next/navigation'
import { useEffect, useState } from 'react'
export default function Update() {
// 상태 변수 설정
const [title, setTitle] = useState('')
const [body, setBody] = useState('')
// useRouter를 통해 현재 라우터 정보 가져오기
const router = useRouter()
// useParams를 통해 URL에서 파라미터(id) 추출
const params = useParams()
const id = params.id
// 컴포넌트가 렌더링 될 때 API로부터 데이터 가져오기
useEffect(() => {
fetch(`http://localhost:9999/topics/${id}`)
.then(res => res.json())
.then(result => {
setTitle(result.title)
setBody(result.body)
})
}, []) // 빈 배열은 컴포넌트가 처음 렌더링될 때만 실행하도록 함
return (
// 폼 제출 시 호출되는 함수 설정
<form
onSubmit={e => {
e.preventDefault() // 폼 제출 시 페이지 새로고침 방지
// 입력된 데이터 가져오기
const title = e.target.title.value
const body = e.target.body.value
// HTTP PATCH 메소드를 이용하여 서버에 데이터 업데이트 요청
const options = {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ title, body }),
}
fetch(`http://localhost:9999/topics/${id}`, options)
.then(res => res.json())
.then(result => {
const lastId = result.id
// 라우터 새로고침 및 목록으로 이동
router.refresh()
router.push(`/read/${lastId}`) // 절대경로
})
}}
>
<p>
<input
onChange={e => setTitle(e.target.value)}
type="text"
name="title"
placeholder="title"
value={title}
></input>
</p>
<p>
<textarea
name="body"
placeholder="body"
value={body}
onChange={e => setBody(e.target.value)}
></textarea>
</p>
<p>
<input type="submit" value="update"></input>
</p>
</form>
)
}
// read/[id]/page.js
// cache: 'no-store' 설정
export default async function Read(props) {
const response = await fetch(
`http://localhost:9999/topics/${props.params.id}`,
{ cache: 'no-store' }
)
const topic = await response.json()
return (
<>
<h2>{topic.title}</h2>
{topic.body}
</>
)
}
◇ cache: 'no-store'
- HTTP 요청 및 응답에서 캐싱을 비활성화하는 명령
- 브라우저는 해당 요청과 응답을 캐시에 저장하지 않고 매번 새로운 요청과 응답을 생성한다.
- 항상 최신 데이터를 받아올 수 있게 되며, 브라우저의 캐시로부터 영향을 받지 않는다.
- 해당 코드가 없다면 브라우저가 네트워크 요청 결과를 캐시에 저장할 수 있으며, 이로 인해 새로고침을 하기 전까지는 이전에 받아온 데이터가 화면에 보일 가능성이 있다. 즉 서버에서 데이터가 변경되더라도 브라우저는 캐시 된 데이터를 우선적으로 사용하려는 경향이 있다.
▶ 삭제 기능 구현
// src/Control.js
'use client'
import Link from 'next/link'
import { useParams, useRouter } from 'next/navigation'
export default function Control() {
const params = useParams()
const router = useRouter()
const id = params.id
return (
<ul>
<li>
<Link href="/create">Create</Link>
</li>
{id ? (
<>
<li>
<Link href={`/update/${id}`}>Update</Link>
</li>
<li>
<input
type="button"
value="delete"
onClick={() => {
const options = { method: 'DELETE' }
fetch(`http://localhost:9999/topics/${id}`, options)
.then(response => response.json())
.then(result => {
router.push('/')
router.refresh()
})
}}
/>
</li>
</>
) : null}
</ul>
)
}
value가 delete인 input태그에 onClick 속성을 설정하여 삭제 이벤트를 구현한다.
DELETE 메서드를 사용하면 해당 리소스를 삭제할 수 있다. fetch 함수를 통해 DELETE 요청을 보낸다.
응답(response)을 확인하고, 응답이 성공한 경우, json()메서드를 사용하여 응답 데이터를 JSON 형식으로 파싱한다.
이후에는 파싱된 JSON 데이터를 사용하여 라우팅과 새로고침을 수행한다. router.push('/')는 루트 페이지로 이동하는 명령이며, router.refresh()는 현재 페이지를 새로고침하는 명령이다.
요약해서 말하자면, DELETE 요처이 성공하면 응답을 파싱하고, 해당 응답 데이터를 기반으로 루트 페이지로 이동하고 현재 페이지를 새로고침한다.
▶ 환경변수
환경 변수는 소프트웨어 애플리케이션에서 사용되는 설정 값들을 저장하고 관리하는 데 사용되는 변수이다. 이러한 값들은 애플리케이션의 동작을 제어하거나 구성하는 데에 중요한 역할을 한다. 환경 변수는 코드에 하드코딩된 값 대신에 사용되며, 다양한 환겨에서 애플리케이션을 실행할 때 설정을 변경하거나 관리할 수 있게 해준다.
.env | 일반적으로 사용되는 환경 변수 파일로, 애플리케이션 설정을 관리한다. 주로 로컬 개발 환경에서 사용되며, 애플리케이션 코드 내에서 참조된다. |
.env.local | 로컬 환경에서 사용하는 환경 변수 파일로, '.env' 파일과 비슷하지만 로컬에서만 적용된다. |
.env.development / .env.text / .env.production | 각각 개발, 테스트, 프로덕션 환경에서 사용되는 환경 변수 파일이다. 해당 환경에 따라 다른 설정을 사용할 수 있다. |
.env.$(NODE_ENV) | Node.js 환경변수 'NODE_ENV'의 값에 따라서 동적으로 로드되는 환경 변수 파일이다. 예를 들어, '.env.development'와 같이 사용할 수 있다. |
.env.local.$(NODE_ENV) | Node.js 환경변수 'NODE_ENV'의 값에 따라 로컬 환경에서 동적으로 로드되는 환경 변수 파일이다. |
.env.$(CUSTOM_NAME) | 사용자가 임의로 정의한 이름에 따라 로드되는 환경 변수 파일이다. 예를 들어, '.env.staging'과 같이 사용자가 정한 이름으로 사용할 수 있다. |
환경 변수 파일을 주로 키-값 형식으로 구성되며, 파일 내에서 설정된 환경 변수는 애플리케이션 코드에서 '$(process.env.KEY_NAME) 형식으로 참조되어 사용된다. 이를 통해 애플리케이션 설정을 관리하고 다양한 환경에서 일관된 동작을 유지할 수 있다.
NEXT_PUBLIC_API_URL=http://localhost:9999
위의 코드는 'NEXT_PUBLIC_API_URL' 환경 변수를 설정하고 있다. 이 변수는 프론트엔드 코드에서 사용될 수 있고, 해당 URL은 로컬 환경에서 API에 접근하는 데 사용될 것이다. 해당 변수를 '$(process.env.NEXT_PUBLIC_API_URL)' 형식으로 참조하여 사용할 수 있따.
Configuring: Environment Variables | Next.js
Next.js comes with built-in support for environment variables, which allows you to do the following: Next.js has built-in support for loading environment variables from .env.local into process.env. This loads process.env.DB_HOST, process.env.DB_USER, and p
nextjs.org
'React' 카테고리의 다른 글
[React] components, props and JSX (0) | 2024.01.08 |
---|---|
[React] 리액트와 같은 프레임워크는 왜 존재할까? (1) | 2024.01.01 |
[React] Gatsby (0) | 2023.08.16 |
[React] Animations (0) | 2023.08.14 |
[React] Drag and Drop (0) | 2023.08.12 |