Next.js 13 <생활코딩>

걸음걸음·2023년 8월 31일
0

NextJs

목록 보기
1/1

생활코딩의 NextJS 강의를 들으며 정리

Next.js 란

React 서버 사이드 렌더링 프레임 워크

  • 서버에서 자바스크립트를 로딩, 클라이언트 측에서 자바스크립트를 로딩하는 시간 단축

  • 서버 측에서 html, css, 자바스크립트를 만들어 컨텐츠를 직접 업로드해 SEO 용이함

  • 직관적인 페이지 기반 라우팅 시스템(동적경로 지원)

  • 사전 렌더링, 정적 생성(SSG) 및 서버측 렌더링(SSR) 페이지별로 지원

  • 더 빠른 페이지 로드를 위한 자동 코드 분할

  • 최적화된 프리페치를 사용한 클라이언트측 라우팅

  • 내장 CSS 및 Sass 지원, 모든 CSS-in-JS 라이브러리 지원

  • Fast Refresh를 지원하는 개발환경

  • Serverless Functions로 API 엔드포인트를 구축하기 위한 API 경로

  • 확장 기능

Next.js 설치, 실행

설치

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 & page

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>
  )
}

Routing

라우팅

사용자가 접속한 URL의 path에 따라 콘텐츠를 응답해주는 작업
http://a.com/board/topics/
a.com = domain
/board = segment
/topics = segment
(/board/topics/ = path)

Next.js의 라우팅

/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}</>
  )
}

Single Page Application

하나의 페이지에서 모든 작업을 처리하는 앱
서버로부터 데이터를 가져올 때는 ajax와 같은 방법을 사용, 동적으로 로딩

  • 링크를 클릭했을 때 페이지 전체 리로딩이 일어나지 않고 필요한 콘텐츠만 로딩
  • 이미 방문한 페이지는 캐싱이 되어 재다운로드가 일어나지 않음
  • 미리 페이지를 로드하여 실제 요청이 있을 때 즉시 응답

a 태그 대신 Link 사용
이동 버튼을 누르기 전에, 백그라운드에 미리 해당 페이지를 다운

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <h1><Link href="/">WEB</Link></h1>
        ...
      </body>
    </html>
  )
}

정적인 자원 사용 - public

이미지, robots.txt, favicon.ico 와 같은 파일 : static assets

/public 폴더에 이미지와 같은 파일을 위치
ex) /public/hello.png => <img src='/hello.png' /> 방법으로 사용

backend

Next.js로 api를 구축하는 방법

json-server

npx json-server --port 9999 --watch db.json

  • --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>
  )
}

서버 컴포넌트는 모든 작업을 서버쪽에서 처리, 결과만 클라이언트로 전송(서버사이드 렌더링)

  • 간결한 코드 : useEffect 등의 훅을 사용하지 않음, 코드가 더 간결해져서 코드의 유지관리가 용이해지고 버그발생확률이 감소
  • 빠른 데이터 엑세스 : 서버 컴포넌트는 서버와 데이터베이스가 가까이 위치, 더 빠른 속도로 접근 가능
  • 보안 : 클라이언트에 민감한 정보를 전송하지 않아 안전하게 처리 가능
  • 향상된 성능 : 클라이언트로 js 코드를 전송하지 않아 전송량을 줄이고 클라이언트의 부하도 줄일 수 있음

읽기 기능 구현

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}
  </>
}
  • 강력 새로고침(캐쉬 삭제) : Cmd + Shift + R / Ctrl + Shift + R

생성 기능 구현 - Client component

'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'}를 사용하면 캐쉬를 사용하지 않게 됨
    ex) const resp = await fetch('http://localhost:9999/topics/', {cache:'no-cache'})

필요할 때 update, delete 기능 보이기

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(process.env.APIURLtopics/{process.env.API_URL}topics/{id})

  • client component에서 변수를 사용하기 위해선 NEXT_PUBLIC_ 접두사 필요
    기본적으론 server component에서 사용

nextjs는 .env.local은 버전관리 할 필요가 없도록 기본 설정
샘플 정보를 담을 때는 .env.local.example 형식으로 사용(공개용)

profile
꾸준히 나아가는 개발자입니다.

0개의 댓글