본문 바로가기

WINK-(Web & App)/WINK 공홈 부수기

WINK 공홈 Next.js 스터디 부터 시작하기 💫 (frontend/조다운)

반응형

🥨 개요

WINK 에서 공식 홈페이지 리뉴얼을 시작하기를 앞두고, 리액트와 뷰만 써본 나에게 next.js 로 배포된 공식 홈페이지를 리뉴얼 하는 미션이 주어졌다 (내가 주웠다) 마침 노마드 코더에 부담스럽지 않은 Next.js 무료 강의가 있어 수강하고 블로깅 하게 되었다. 틈틈히 보다 보니까 오래 걸리게 된 이슈가 있다 진짜 열심히 봤음

팀장이 (내가) 일요일 까지 블로깅 꼭 하라고 했는데 일요일이 되자 그런 규칙 만든 팀장 (나) 이 원망스러웟다
포기할 뻔

 

암튼 공홈 부수기 화이팅~

우리 공홈 정상영업 합니다.

🥨 INTRODUCTION

라이브러리 vs 프레임워크

라이브러리는 사용자가 전체적인 흐름을 만들고 필요한 라이브러리를 자유롭게 호출하여 사용하는 것이고 프레임워크는 주도권을 프레임워크가 가지고 있으며 프레임워크의 흐름에 맞게 사용자가 필요한 코드를 작성하는 것이다. 즉, 라이브러리의 주도권은 사용자가 가지고 있고 프레임워크의 주도권은 프레임워크가 가지고 있어서 사용자는 프레임워크를 사용할 때 프레임워크의 규칙을 잘 지켜서 사용해야 한다.

Old vs New

Next.js 에는 App Router, Pages Router 두 개가 존재한다.

Next.js 11,12,13 을 사용하다가 14 를 사용하는 경우 router 와 optimization 은 동일하게 작동한다. 

하지만 14 부턴 app router 가 있고 이는 app 폴더에서 찾을 수 있다.

기존 버전으로 만들어진 화면은 (pages router) 그대로 두고 새로 만들어지는 화면만 app router 로 만들면 된다.

 

App Router 에서 routing 하는 법과 data fetching 하는 방법이 아주 많이 바뀌었다.

getStaticProps, getSErverSideProps, getStaticPaths 같은 것응은 사라져버림

 

모두 migrate 할 생각 하지 마 둘 . 다 쓸 수 있음 언젠간 pages router 사라지겠지만 아직은 아님

Project Setup

mkdir learn-nextJS14

npm init -y

npm install react@latest next@latest react-dom@latest

 

react UI 와 모든 것을 구성하고

react-dom 은 그것을 브라우저의 Document Object Model (DOM) 에 렌더하는 역할을 한다.

 

NextJS 는 웹사이트를 빌드할 때 항상 첫 번째 페이지를 app/page 위치의 파일에서 찾으려 하기 때문에 이름을 잘 지켜서 사용해야 한다.

app/page 만 있는 상태에서 npm run dev 하면 Next.js 가 app 폴더에 자동으로 layout 파일을 만들어준다. 웹 실행에 layout 파일은 필수이기 때문!

 

🥨 ROUTING

routing, navigation, layout, client, server component

app 폴더 속 폴더 -> url 을 위함!

그 안에 page.tsx 를 넣는 행위 -> 실제로 보여지는 페이지를 만들기 위함!

app/about-us/company/sales/page.tsx 이면 그대로 실제 페이지의 url

page.tsx 가 없는 폴더는 그저 경로의 일부분이 됨 company 라는 경로가 실제로 보이게 만드려면 해당 폴더 안에서 page.tsx 를 꼭 생성해야 함

실제 페이지를 만들 때 무조건 파일 이름은 page.tsx 여야함

not-found routes

app 폴더에 not-found.tsx 파일을 추가해주면 존재하지 않는 주소에 접근했을 때 해당 페이지를 보여준다.

특별한 이름의 파일 3 개

not-found.tsx, page.tsx, layout.tsx

Navigation

a href 를 사용하는 것은 브라우저 시스템을 이용하는 것이기 때문에 Link 를 사용하여 페이지를 이동한다.

매 페이지 마다 Navigation 컴포넌트를 붙여넣어 사용하는 것은 효율적이지 못하다. -> 그럼 어떻게? (layout 사용)

 

💡 유저가 어느 페이지에 있는지 이모지를 사용하여 navigation 바에 표시하고 싶다면? Next.js 에는 우리에게 url 에 관한 정보를 알려주는 hook 들이 존재한다.

usePathname

path name 이란 user 가 현재 머무르고 있는 url 이다.

Error:  × You're importing a component that needs usePathname. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.

오직 client components 에서만 작동한다. 

최상단에 use client 를 추가하였다.

왜 꼭 use client 를 추가해줘야 할까? 그 이유를 알려면 next.js 이 application 을 render 하는 방식에 대해 알아야 한다. 여기서 rendering 이란 next.js 가 우리의 react component 를 가져와서 브라우저가 이해할 수 있는 html 로 변환하는 작업을 말한다.

SSR vs CSR

평범한 react 가 render 되는 방식은 client rendering 이다. 즉 브라우저가 rendering 작업을 한다는 것이다. 사용자 브라우저인 client 단에서 모든 rendering 작업을 수행한다는 뜻이다. 

 

CSR 의 단점은 유저가 페이지에 도착한 시점에는 페이지가 빈 화면인 것이다. 브라우저가 모든 JavaScript 파일을 다운로드하고 실행한 후에야 화면이 보여진다. 따라서 실행 전 다운로드하는 시간이 소요된다. (브라우저의 JavaScript 엔진에 의해 추가된 것)

그리고 SEO 검색 엔진 최적화가 어렵다. Google 은 페이지의 html 을 보는데 csr 일 때 js 파일을 다운로드 받기 전엔 빈 html 으로 보여지기 때문이다. 

 

Next.js 에서는 자동으로 Server Side Rendering 되는데 이 경우 JavaScript 가 별개로 로딩되는 시간을 기다릴 필요가 없다. html 파일이기 때문에 JavaScript 가 업성도 괜찮당 (적어도 최초 application 의 UI 빌드에서는 JS 에 의존하지 않는다)

나중에 interactivity 와 hydration 에 대해서도 알아보겠다

 

Next.js 의 모든 컴포넌트에 대해 Server Side Rendering 된다. (페이지 최상단에 "use client" 가 써져 있더라도)

모든 컴포넌트와 페이지는 Backend 에서 렌더링되어 html 이 된 후 그 html 결과물이 브라우저로 넘어간다.

Hydration

사용자가 최초 로딩된 html 을 본 뒤에 어떤 일이 발생하는지, React 가 언제 활성화 되는지의 과정을 Hydration 이라고 한다.

JavaScript 가 비활성화 되었을 때 navigation bar 를 클릭하면 a 태그를 통해 페이지 전체가 새로고침 되는데 (hard refresh) 활성화 되었을 때는 새로 고침 되지 않고 클릭했을 때 React 컴포넌트가 로딩된다.(client side navigation) (클릭할 때마다 react 가 끼어든다!) 페이지 전체를 reload 하지 않고 빠르게 navigate 할 수 있게 됨 이제 더 이상 평범한 a 태그는 없어

 

/about-us --------> Boring HTML --------> 사용자 Happy --------> init(Boring HTML)

react component 가 로딩되었을 때가 되어서야 완전히 interactive 해짐

/about-us --------> <button>0</button> --------> 사용자 Happy --------> <button onClick

 

다시 말해서 hydration 은 단순 HTML 을 React application 으로 초기화 하는 작업이다.

'use client'

모든 컴포넌트에서 Server Side Rendering 은 일어나지만 Hydration 은 모든 컴포넌트에서 일어나진 않는다. 지루한 html 이 react component 가 되는 행위인 hydration 은 코드 최상단에 use client 가 적혀 있는 경우에 일어난다. 

'use client' 는 컴포넌트에게 '이봐 이 component 는 client 에서 interactive 해야해 즉 hydrated 되어야해' 라고 알려준다.

 

초기 load 에서 next.js 는 component 를 render 하고 지루한 html 을 사용자에게 주고 나서 eventListener 들을 추가할 component 를 hydrate 할 것이다. (코드 최상단에 use client 가 있을 경우에만 해당함)

 

interactive 한 componet 를 사용하려면 use client 를 작성해줘야 한다는 뜻이다. 단지 client 단에서 rendering 된다는 말이 아니라 backend 에서 render 되고 frontend 에서 hydrate 됨을 의미한다. 워딩이 use client 가 아니라 use hydrate 였다면 더 좋았을텐데...

 

use client 를 작성하지 않으면 그 컴포넌트는 기본적으로 server component 가 된다.

 

주의할 것은 use client 를 작성한다고 ssr 이 작동하지 않는다는 것이 아니라, rendering 된 후 hydrate 가 진행된다는 것이다. 

 

만약 컴포넌트가 client 에 딱 한 번만 render 되고 다시는 render 될 일이 없다면, 즉 useState 나 onClick events 같은 것이 없을 경우 자바스크립트 코드를 다운 받을 필요가 없기 때문에 더 빨라진다. 사용자는 use client 를 가진 components 의 JavaScript 코드만 다운 받으면 된다. 

Recap

server component 에서 client component 를 호출할 수 있을까? 물론이다!  하지만 client component 안에 server component 를 호출하는 것은 불가능하다. use client 가 선언된 코드에서는 server component 를 불러도 client component 가 된다. 

 

server component 에서 data 를 fetch 하기 위하여 API key 를 사용하여 호출했을 때 이는 브라우저에서 절대 나타나지 않기 때문에 보안에 신경 쓰지 않아도 된다. 

Layouts

Next.js 는 layout 컴포넌트를 먼저 렌더링한 후 url 을 확인하고 내부 컴포넌트를 layout 컴포넌트 안에 렌더링한다.

특정 페이지를 위한 레이아웃을 만들 수도 있다. about-us 만을 위한 레이아웃이 만들고 싶다면 about-us 폴더 안에 layout.tsx 파일을 또 만들어준다. 그리고 나서 html 태그와 body 태그를 지우고 레이아웃을 작성한다. 

export default function Layout({ children }: { children: React.ReactNode }) {
    return (
        <div>
            {children}
            &copy; Next JS is great!
        </div>
    )
}

 

레이아웃은 서로 상쇄하지 않고 중첩된다. Next.js 는 url 을 통해 폴더로 들어가서 그 폴더에 레이아웃이 있는지 확인한다. 만약 레이아웃이 있다면 그 레이아웃을 밖에 있는 다른 레이아웃 안에 렌더링한다. 

Metadata

route groups 는 우리의 routes 들을 그룹화 해서 logical groups 으로 만들 수 있는 기능이 있다. 예를 들어 중첩 레이아웃을 원하지 않는 경우에 route groups 를 사용할 수 있다. 

 

route groups 을 사용하여 route 들을 정리하는 방법 -> 먼저, 폴더 이름을 괄호로 묶어줘야 한다. 괄호로 묶은 폴더는 url 로 사용되지 않는다. 

/home 이 생성되지 않는다.

layout 과 not-found 는 기본적으로 모든 routes 에 공유되므로 그냥 app 에 둔다. metadata 도 중첩될 수 있지만 실제로는 중첩되지 않고 병합이 된다. 또 페이지나 레이아웃만 메타데이터를 내보낼 수 있다. 컴포넌트에서는 metadata 를 내보낼 수 없고 또 metadata 는 서버 컴포넌트에서만 있을 수 있다. 직접 head 에 꼭 무언가 넣을 필요가 없어~ (설치하거나 컴포넌트를 사용할 필요도 없음) 단지 해야 할 일은 메타데이터라는 개체를 내보내는 것 뿐이다. metadata!!

export const metadata: Metadata = {
  title: {
    template: "%s | Next Movies",
      default: "Loading..."
  },
  description: 'Generated by Next.js',
}

metadata 에서 template 을 사용하면 똑같은 걸 계속 반복하지 않아도 된다. 

Dynamic Routes

/about-us : static route

/movies/(영화 코드 등 변수) : dynamic route

export default function MovieDetail({ params: {id},}: {
    params: { id: string };
}) {
    return <h1>Movie</h1>;
}

<MovieDetail params={{id: 121212}} /> 와 같은 식

🥨 DATA FETCHING

introduction

- data fetch, streaming, suspense, loading fallback, error boundary

react 에서 data fetch 하던 원래 방법

react 에서 별도의 라이브러리 없이 data fetch 진행하던 코드는 아래와 같다. 이때에는 브라우저가 API 요청을 보낸다. 

하지만 모든 것을 client 에서 fetch 하는 것은 좋은 생각이 아니다.

client 에서 react 가 작동하기 때문에 react 는 항상 API 를 사용하고 API 가 데이터베이스와 통신해야 했다. 

뿐만 아니라 react 처럼 data fetching 하면 컴포넌트가 처음엔 비어 있기 때문에 꼭 useState 를 사용해야 하고 loading 상태를 직접 구현해야 한다. client component 에서는 metadata 도 사용할 수 없다. 즉 fetch 와 response 를 await 해야 하고 state 를 다시 set 해야 했다. 

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

export default function Page() {
  const [isLoading, setISLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  const getMovies = async () => {
    const response = await fetch('https://nomad-movies.nomadcoders.workers.dev/movies');
    const json = await response.json();
    setMovies(json);
    setISLoading(false);
  };
  useEffect(() => {
    getMovies();
  }, []);
  return <div>{isLoading ? 'Loaading...' : JSON.stringify(movies)}</div>;
}

Server Side

async 를 사용하는 이유~ await 를 사용하기 위해서임 어떤 일이 발생하기를 기다리려고 await 를 사용할 때 부모 함수에 항상 async 가 있어야 함 왜징... 아 애기들이 왜 맨날 당연한 거 물어보는지 알겠다 난 맨날 이해 못해슨데...ㅜ 당연...당연한 거니까! 이랬는데 ...

 

내 블로그 꼼꼼히 읽어준 사람은 댓글로 알려줘~~ async await 에 대해서,,,

아래 코드는 브라우저가 아니라 서버에서 data 가 fetching 된다. 그래서 network 에도 API 호출된 흔적이 안 보임...! 

export const metadata = {
  title: 'Home',
};
const URL = 'https://nomad-movies.nomadcoders.workers.dev/movies';

async function getMovies() {
  console.log("I'm fetching!")
  return fetch(URL).then((response) => response.json());
}
export default async function HomePage() {
  const movies = await getMovies();
  return <div>{JSON.stringify(movies)}</div>;
}

그리고 브라우저 콘솔이 아니라 서버 콘솔에만 I'm fetching 이 실행된다. 신기하잔아...

server 에서 fetching 할 때 로딩 상태가 보이지 않는데 없어진 것은 아니다. 아주 빠르게 진행되는 것이다. 왜? server component 에서 Next.js 가 사용자가 fetch 한 것을 캐싱하여 기억하기 때문이다. 첫 번째 fetch 만 진짜 fetch 이다. (진짜 API 에 요청하는 것)

Loading Components

하지만 백엔드에서 API 를 호출하는 데에 시간이 오래 걸리면 브라우저 페이지에 아무 것도 뜨지 않는다. (로딩 중일 때) 그리고 나는 네비게이션 바가 언제나 상단에 존재했으면 좋겠다.

요렇게 loading.tsx 만들어주면 api 호출이 느려질 때 해당 컴포넌트가 뜨게 된다. 로딩 되는 동안에도 무사히 네비게이션 바가 보이게 되는 것이다. Next.js 가 하는 일은 웹사이트의 일부를 천천히 보내는 것이다. 첫번째로는 layout 이나 navigation 을 먼저 보내고 그 다음에 loading component 이 보여지고 그 다음에 결과값으로 판단되는 것을 렌더링한다. 

 

<Loading />

const html = await HomePage()

isLoading ? <Loadinng /> : html

 

loading 파일 이름과 위치가 지켜져야 의도대로 기능한다. (loading.tsx, page 옆에 위치하기)

Parallel Requests

여러 개의 data fetching 이 일어날 때 아래 코드 처럼 호출할 경우 순서대로 호출된다. 앞의 것이 시간이 오래 걸리면 뒤의 것도 밀리게 되고 다소 오랜 시간이 걸리게 된다. 그래서 병렬 호출이 가능하면 좋겠다.

import { API_URL } from '../../../(home)/page';
async function getMovie(id: string) {
  console.log(`Fetching movies: ${Date.now()}`);
  await new Promise((resolve) => setTimeout(resolve, 5000));
  const response = await fetch(`${API_URL}/${id}`);
  return response.json();
}

async function getVideos(id: string) {
  console.log(`Fetching videos: ${Date.now()}`);
  await new Promise((resolve) => setTimeout(resolve, 5000));
  const response = await fetch(`${API_URL}/${id}/videos`);
  return response.json();
}
export default async function MovieDetail({ params: { id } }: { params: { id: string } }) {
  console.log('start fetching');
  const movie = await getMovie(id);
  const videos = await getVideos(id);
  console.log('end fetching');
  return <h1>{movie.title}</h1>;
}

Promise.all

promise.all 을 사용한다면 getMovie 와 getVideos 를 await 할 것이다. 그래서 짠 아래와 같이 바뀐다.

promis.all 은 배열로 호출된 api 의 결과 값을 우리에게 보내줄 것이다. 차례대로 getMovies 함수의 결과 값, getVideos 함수의 결과 값을 array 로 보내준다!!!

export default async function MovieDetail({ params: { id } }: { params: { id: string } }) {
  console.log('start fetching');
  const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)]);
  console.log('end fetching');
  return <h1>{movie.title}</h1>;
}

이렇게 하면 두 data fetch 가 동시에 시작한다. 그리고 5 초 뒤 동시에 끝난다. 

Suspense

fetch 함수들을 분리하는 법을 알아보자 데이터 소스가 여러 개라면 suspense 를 사용해야 한다. 

현재는 getMovie 와 getVideos 가 끝날 때까지 기다려야 한다. 하지만 어느 하나가 먼저 끝나면 먼저 UI 에 보이도록 함수들을 분리하고 싶다. 

컴포넌트 폴더에 각각 함수를 분리해서 만들어준다. 각 component 는 자신에 관한 데이터만 fetch 한다. 둘 다 async 이고 await 해서 데이터를 fetch 했다. 

MovieDetail 에서 해당 컴포넌트들을 suspense 로 감싸준다. 그 이유는 각각 컴포넌트가 await 하기 때문이다. Suspense 가 데이터를 fetch 하기 위해 이 안의 component 를 await 하는 것이고 fetch 하는 중에는 fallback 내용이 보여진다.

suspense component 에는 fallback 라는 prop 이 있는데 component 가 await 되는 동안 표시할 메시지를 render 할 수 있게 해준다. (로딩 상태도 분리)

Error Handling

가짜 에러 보내기

이 화면은 개발을 위한 화면이기 때문에 사용자에게 보이지 않는다. 그래서 error.tsx 를

loading.tsx 와 같이 추가해준다.

error.tsx 는 꼭 use client 를 추가해줘야 한다. 

'use client';
export default function ErrorOMG() {
  return <h1>lol something broke...</h1>;
}

 

🥨 DEPLOYMENT

Introduction

vercel 계정 만들기 ✔️

github 에 코드 올리기 ✔️

CSS Modules

next.js 에서는 별도의 다운로드나 설정 없이 css modules 를 사용할 수 있다.

css 모듈화 하기 전에 해야할 일은 global style 을 만드는 것이다. global.css 에서 특정 컴포넌트의 스타일링은 하지 않는다. 

 

navigation 에 스타일을 주려면 navigation.module.css 파일을 생성하고, navigation.tsx 에서 import 한다. 그리고 className 을 사용하여 스타일을 적용할 부분을 지정해준다. 이렇게 하면 class 끼리 충돌이 일어날 일은 절대 없겠지

styles.nav!!

<Link> 말고 useRouter 를 사용하는 페이지 이동 방식도 존재한다. 

import { useRouter } from 'next/navigation';

export default function Movie({ title, id, poster_path }: IMovieProps) {
  const router = useRouter();
  const onClick = () => {
    router.push(`/movies/${id}`);
  };
  return (
    <div className={styles.movie}>
      <img src={poster_path} alt={title} onClick={onClick} />
      <Link href={`/movies/${id}`}>{title}</Link>
    </div>
  );
}

 

 

이후 나머지 css 또한 적용 후 vercel 을 이용하여 배포하였다. 함 들어가보아라!-! (안 열릴 수도 있음)

https://nextjs-movies-rose.vercel.app/

 

Home | Next Movies

 

nextjs-movies-rose.vercel.app

 

🥨 정리

제법 리액트와 비슷하면서도 신기한 것들이 많다. nuxt 같기도 하고 짱 편하다 공홈에서 넥스트 써보기? 완전 럭키비키잔앙 🍀🍀 강의에서 같은 내용도 다른 말로 여러 번 반복해줘서 자동 복습이 되어 좋았다. 히히 심지어 무료 강의임... 하지만 정리하면서 강의 보니까 오래...걸리긴 해따...

그치만 끝까지 강의를 보았기 때문에 뿌듯하다 국굳 열심히 했다고요 다들 화이팅 아자아자

 

api -  https://nomad-movies.nomadcoders.workers.dev/

 

Next.js 시작하기 – 노마드 코더 Nomad Coders

NextJS for Beginners

nomadcoders.co

 

반응형