본문 바로가기
Web Study/Next.js

Next.js로 SNS x.com 클론코딩하기 - 1

by 쿠리의일상 2024. 1. 18.

https://www.inflearn.com/course/next-react-query-sns%EC%84%9C%EB%B9%84%EC%8A%A4/dashboard

 

Next + React Query로 SNS 서비스 만들기 강의 - 인프런

리액트18 & 넥스트14 & 리액트쿼리5 & Next Auth5 & MSW2 & socket.io4 & zustand 스택으로 트위터(X.com)와 유사한 SNS 서비스를 만들어봅니다. 끝으로 검색엔진 최적화를 위한 SSR까지!,...

www.inflearn.com

해당 강의를 보고 정리한 내용입니다.

 

왜 Next.js 인가?

이번에 들어가게될 프로젝트에서 React 기반의 Next.js 를 사용하기로 하였습니다. SEO가 장점이긴 하지만 제가 맡을 프로젝트에선 중요사항은 아니었고, CSR보다는 SSR이 필요했기 때문에 채택하게 되었습니다.

익히 들어보셨겠다시피 Next.js는 서버 사이드 렌더링으로 모든 페이지를 미리 렌더링(HTML 생성)하여 생성된 HTML에서 필요한 최소한의 JS 코드와 연결 후 실행되는 구조를 가지고 있습니다.

공부 방법은 우선 가장 기본적으로 공식 문서를 봐줍니다. 1회독으론 이해가 안되는게 정상이고.. 공식 문서상 모르는 내용은 따로 서칭도 해가며 확인하게 되었습니다. 그러다가 공식문서에 나온 내용을 어떻게 사용하는가 확인이 필요했기 때문에 혼자 사이드 프로젝트를 하며 삽질을 조금 하다가... 강의를 수강해야겠음을 느꼈습니다.

특히나 Next.js 13버전부터 App router를 채택하며 사용법이 크게 변화된 점이 무척이나 헷갈리는 요소가 되었습니다. 조금이라도 예전 게시글을 참고하면 Page router 방식으로 설명하고 있어 현재의 사용법과 상이한 부분이 있었으니까요.

ISR의 getStaticProps 함수 또한 13버전 이후로 사라지고 대신 React18 의 서버 컴포넌트와 클라이언트 컴포넌트로 나눠서 사용되는 부분 또한 너무 헷갈렸습니다... 공공 API를 fetching 해주고 싶은데 이제 getStaticProps 함수는 안쓴다길래 '??...' 띠용하고 어떻게 쓰는지 또 서칭하고... 결국은 찾아냈으나 퇴근 후에 조금씩 진행하는 바람에 그 시행착오가 좀 길었습니다.

특이하게도 넥스트는 fetch 를 사용하는 것을 권장하더라구요. 제가 알던 fetch는 매번 설정해줘야 할 게 너무 많아서 axios 를 사용하는게 당연했는데... 이번 기회에 fetch도 공부하는 계기가 되었습니다.

 

뭔가 길게 말했는데 Next.js 를 사용하는 이유를 보면 아래와 같습니다.

  1. SEO
  2. SSR / ISR
  3. 페이지 초기 로딩 속도 개선
  4. 자동 코드 분할, 환경 변수, 레이아웃 등 개발 생산성
  5. 확장성
  6. 파일 시스템 기반 라우팅과 동적 라우팅
  7. 프리페칭 등의 캐싱 처리

 

Next.js version13~

위에도 기재했다시피 Page router 에서 App router 로 전환되었습니다. 레이아웃과 권한, 캐싱 처리에서 변화가 있다고 합니다. SSR 특성상 서버의 부하가 커질 수밖에 없는 구조인데 어떻게 잘 처리가 되나보더라구요.

해당 강의는 13 버전 이후로 진행됩니다. 현재 버전은 14입니다.

 

일단 프로젝트 시작부터

npx create-next-app@latest

프로젝트 이름을 포함한 여러 질문들이 나오는데 적절히 선택해줍니다. 

사실상 기본 세팅은 더 크게 건들게 없습니다. next.config.js 를 수정하는 일은 어차피 차차 플젝을 진행하며 채워나가야할 내용이니까요. https://nextjs.org/docs/app/api-reference/next-config-js/images

 

next.config.js Options: images | Next.js

Custom configuration for the next/image loader

nextjs.org

eslint와 prettier 등 기본적으로 세팅이 되어서 좋더라구요. vite로 리액트 프로젝트를 만들면 필수였거든요.

 

npm run dev

일단 개발 환경으로 실행해줍니다.

src
ㄴ app
	ㄴ ...
    ㄴ page.tsx ---> localhost:3000/
    ㄴ layout.tsx ---> localhost:3000/

다른 폴더들은 일단 무시하고 간단하게 보이는 페이지 구조만 본다면 위와 같을겁니다. npm run dev를 했을 때 제일 먼저 보이는 페이지는 /app/page.tsx와 /app/layout.tsx로 매 폴더마다 page.tsx, layout.tsx 페이지를 만들 수 있으며 자동 라우팅됩니다. 유용하죠? 리액트에선 매번 이 작업을 위해 루트 App 컴포넌트에 Router 관련 컴포넌트를 감싸주고 Route 해줬는데...

그리고 app 폴더의 layout.tsx 파일은 가급적 안 건드시는게 좋습니다. 특히 html 부분이 빠지면 오류가 발생하므로 깔끔하게 정리만 하시는 걸 추천드립니다. (이 문제 때문에 서칭했던 적이 있어서..)

 

강의 섹션 1 시작 - 기획자와 디자이너가 기획서를 던져주었다

강의 컨셉이 재미있어요. 하나씩 차근히 섹션을 돌파해나가봅시다.

https://twitter.com/home

 

X

 

twitter.com

프로젝트는 생성했지만 막막합니다. 클론 코딩은 실제 사이트를 보며 최대한 유사하게 따라는거니까 x를 확인해봅니다. 이를 바탕으로 app 폴더 아래에 파일 시스템 구조를 만들면 아래와 같습니다.

 

폴더 구조 짜기

가장 먼저 크게 로그인 후와 로그인 전 레이아웃이 상이하므로 이를 반영하면,

ㄴ app
   ㄴ (afterLogin)
    	ㄴ [username]
        	ㄴ status
            	ㄴ [id]
        ㄴ compose
        	ㄴ tweet
        ㄴ explore
       	ㄴ home
        ㄴ message
        ㄴ search
        
    ㄴ (beforeLogin)
    	ㄴ i
        	ㄴ flow
            	ㄴ login
                ㄴ signup

여기서  [ ]와 () 가 보일 것입니다.

소괄호 () 의 경우, 주소 라우팅에 포함은 안되지만, 고유한 layout.tsx 를 만들어줄 수 있는 그룹핑용 폴더라고 생각하시면 됩니다. 즉 위의 explore 폴더의 라우팅 결과 'afterLogin/explore'  가 아닌 '/explore'가 될 것입니다. 

대괄호 [ ]의 경우 동적 라우팅을 위한 규칙으로, page.tsx 내 대괄호 안의 변수를 가져와서 사용자나 특정 게시글의 정보를 다르게 보여줄 수 있습니다.

이렇듯 넥스트의 폴더 구조는 여러 방법이 있겠지만 레이아웃에 따라 설계해주는 것이 좋다고 합니다.

 

레이아웃에 있어 layout.tsx 와 template.tsx 두 종류로 가능한데 둘의 차이점은

  • layout.tsx : 페이지가 변경 되어도 리렌더링 되지 않음
  • template.tsx : 페이지가 변경 되면 리렌더링(Re-Mount) 됨

즉 둘을 혼용해줄 순 없으므로 상황에 따라 선택해서 사용하면 됩니다.

 

넥스트에선 a태그 대신 Link 컴포넌트를

기존 a태그는 페이지가 새로고침됩니다. 하지만 페이지 새로고침은 가급적 안하는 것이 맞기 때문에 넥스트에서 제공하는 Link를 사용해줍니다.

import Link from 'next/link'

 

리다이렉트

x 페이지를 들어가보면 로그인 시 /login 으로 기본 설정 되어 있음을 알 수 있다. 하지만 후에 바로 /i/flow/login 으로 이동하게 되는데 이를 반영하여 /app/login 폴더 안에 next/navigation 의 redirect 를 사용해줍니다.

import { redirect } from 'next/navigation';

export default function LoginPage() {
  redirect('/i/flow/login');
}

 

public 폴더

기존 만든 폴더 구조를 건드리지 않았다면 루트 폴더 아래 public 폴더가 존재합니다. 이 폴더는 정적으로 이미지 등의 소스를 빌드할 때 포함되는 부분입니다. 그러니 꼭 필요한 요소만 넣어주는게 좋습니다.

https://github.com/ZeroCho/next-app-router-z

 

GitHub - ZeroCho/next-app-router-z

Contribute to ZeroCho/next-app-router-z development by creating an account on GitHub.

github.com

관련 강의 깃헙으로 소스를 다운 받읍시다.

강의 내 상세 코드도 중요한거나 메모해둘 내용이 아니면 위에 다 있기 때문에 작성하지 않을 예정입니다.

 

Image 태그

import Image from 'next/image';

넥스트의 Image 태그는 png 파일 등의 이미지를 import 한 다음 src 속성에 넣어서 사용해줄 수 있습니다.

그리고 기본적으로 이미지를 최적화 해주므로 기존에 사용하던 img 태그는 사용을 지양합니다.

 

tailwind css X -> CSS Module

해당 강의에선 테일윈드, styled component, sass, Emotion... 가 아닌 css module 을 사용한다고 합니다.

테일윈드는 제가 주로 사용하는데 가독성이 안좋긴 하거든요. 작은 플젝이면 모를까 조금만 커져도...

Styled component, Emotion은 넥스트와 호환성이 좋지 않다고 합니다.

그리고 요즘 뜨고 있는 vanilla extract 이 있다고 하는데(처음 들어봤습니다) 이는 Windows 와 문제가 있다고 합니다(??) 제 본컴은 맥이고 회사컴은 윈도우라 조금 띠용하네요 ㅎㅎ

추가로 vanilla extract 은 호환성 이슈가 해결되면 추가 강의가 있다고 합니다. 이 기회에 최신 트렌드 알아가고 좋네요.

https://vanilla-extract.style/

 

vanilla-extract — Zero-runtime Stylesheets-in-TypeScript.

Zero-runtime Stylesheets-in-TypeScript.

vanilla-extract.style

 

CSS Module 은 익히 아는 css 파일의 module 형식으로 파일명.module.css 형태의 파일입니다. 이 파일을 사용하기 위해선

import styles from './page.module.css';

위처럼 컴포넌트 안에 import 하여 사용해줄 수 있습니다.

사용법은 해당 변수(styles) 를 객체처럼 클래스명을 가져와서 사용하면 됩니다.

<div className={styles.right}></div>

위처럼 쓰인다고 다른 곳에서 쓰이는 right 와 혼동되는 문제점은 애초에 import 로 특정 페이지에 적용될 스타일을 지정해서 module 화 하기 때문에 실제 빌드된 내용을 보면 right_어쩌구 이런식으로 보이게 되므로 문제가 되지 않는다고 합니다.

 

dvw, dvh

새로 나온 뷰포트 단위라고 합니다. 처음 들어봤는데 역시 이래서 신기술을 매번 확인하지 않으면 도태되나봐요...;;;

이 단위는 기존 vw, vh 를 대체할 수 있는, 브라우저의 전체화면을 쉽게 채울 수 있다고 합니다. 모바일 상에서도 주소창이 있든 없든 동일하게 전체화면을 채울 수 있다고 합니다. 하지만 아직 지원되지 않는 브라우저도 있고 안드로이드, iOS 차이 때문에 생기는 문제점 때문에 말이 많은거 같습니다. stable 되면 보편적으로 사용되지 않을까 싶습니다.

dv 는 dynamic viewport  의 약자로 현재 보여지는(주소창이 노출되든 아니든) 뷰포트의 높이를 동적으로 가져오는 단위고, 추가로 생긴 다른 단위인 sv는 Short viewport로, 사용자 화면 기준 가장 짧은 뷰포트 값을 가져온다고 합니다. 즉 주소창이 없어져도 기존 주소창의 값을 뺀 나머지 값을 가져온다고 합니다.

lv 는 Large viewport 로 사용자 화면 기준 가장 긴 뷰포트 값을 가져와서 주소창이 있더라도 주소창이 없을 때의 총 화면값을 가져온다고 합니다.

 

parallel routes

x를 보면 로그인, 회원가입 시 주소창 변경은 있지만, 기존의 메인 화면 위에 모달 형식으로 뜨는 것을 확인할 수 있습니다. 

https://nextjs.org/docs/app/building-your-application/routing/parallel-routes

 

Routing: Parallel Routes | Next.js

Simultaneously render one or more pages in the same view that can be navigated independently. A pattern for highly dynamic applications.

nextjs.org

이를 위해 먼저 필요한 것이 병렬 라우트로 제한 사항이 있지만 현재 페이지와 다른 페이지를 동시에 보여줄 수 있게 설정할 수 있습니다.

우선 기존의 페이지를 (beforeLogin) 안으로 옮겨줍니다. 동시에 페이지를 보여주려면 일단 같은 폴더 안에 있어야 하기 때문입니다.

위에 띄어줄 모달 폴더를 골뱅이를 붙여 @modal 로 만들어줍니다. 그런 다음 login 폴더 내 layout.tsx 를 만듭니다.

ㄴ (beforeLogin)
	ㄴ @modal
    	ㄴ page.tsx
    ㄴ login
    	ㄴ layout.tsx
    	ㄴ page.tsx
    ㄴ ...

이런 폴더 구조가 된 것을 확인할 수 있습니다.

 

type Props = {
  children: React.ReactNode;
  modal: React.ReactNode;
};

export default function BeforeLoginLayout({ children, modal }: Props) {
  return (
    <div>
      {children}
      {modal}
    </div>
  );
}

위처럼 layout.tsx에서 모달을 받아와줄 수 있습니다.

여기서 modal 을 가져올 때 오류가 발생하면, 그 이유는 같은 폴더 아래에 있지 않기 때문일 가능성이 큽니다. 즉 병렬 라우트는 layout.tsx 상에서 사용해줄 수 있다는 의미이기도 합니다.

 

use client

바뀐 넥스트에선 서버 컴포넌트와 클라이언트 컴포넌트가 분리되어야 합니다. 이를 위해 새로운 기능인 use client 가 있습니다만 이 개념 때문에 많이 삽질했었습니다. 기본적으로 React에서 사용되던 useState, useEffect 등은 다 클라이언트 환경에서 돌아가는 개념이라 기본이 서버 컴포넌트인 넥스트에선 그냥 사용이 불가하답니다.

  • 서버 컴포넌트
    • 기본 컴포넌트, 넥스트 서버에서 돌아감
    • 서버 컴포넌트는 서버에서 돌아가므로 컴포넌트도 async를 붙여 비동기로 로딩할 수 있음
    •  
  • 클라이언트 컴포넌트
    • 'use client' 로 정의가 필요, 브라우저(클라이언트)에서 돌아감

 

default.tsx

넥스트 내 새로운 요소가 병렬 라우트가 필요 없을 때 만들어주는 컴포넌트라고 합니다. 즉 병렬 라우트의 기본값인 것이죠. 아래처럼 만들어줍니다.

export default function Default() {
  return null;
}

 

모달이 기본적으로 떠야 하는 주소는 /i/flow/login 이므로 @modal 안에는 default.tsx 와 새로이 i/flow/login 폴더를 만들어줘야 합니다. 이를 폴더 구조로 나타내면 아래와 같습니다.

 

현재 상황에서 메인 화면의 로그인을 누르면 주소가 바뀌면서 모달이 뜨는 것을 볼 수 있습니다. 하지만 저희가 원했던 모달 아래에 메인 화면이 보이지 않는 것을 볼 수 있습니다.

이 때문에 추가적으로 병렬 라우트와 함께 인터셉팅 라우트가 필요합니다.

인터셉팅 라우트

서로 주소가 다름에도 페이지가 뜰 수 있는 라우팅이라 생각하면 됩니다.

소괄호와 . 을 사용하여 현재 만들어진 i/flow/login 을 대체할 수 있게 폴더명을 수정하면 되는데, 이 경우 @modal 안의 i의 폴더명을 수정해주면 됩니다. 특히나 병렬 라우트의 @는 주소로 되지 않기 때문에 최종적으로 아래와 같은 구조가 되면 됩니다.

 

이제 메인 화면에서 로그인 버튼을 누르면 메인 화면 위로 로그인 모달이 뜨게 됩니다.

하지만 현재 주소창은 http://localhost:3000/i/flow/login 므로 새로고침을 하게되면(주소창으로 직접 접근하게 되면) 모달창이 아닌 /i/flow/login/page.tsx가 뜨게 됩니다. 그러므로 기존 라우트도 남겨둬야 합니다. 위의 경우 병렬 라우트와 인터셉팅 라우트가 섞여서 Link에 의해 가로채기 당해 모달처럼 보이게 된 것뿐입니다.

주의할 점 하나는, 서버에서 리다이렉트 하게 되면 인터셉팅이 정상적으로 작동하지 않을 수 있다고 합니다.

 

private folder

폴더 이름 앞에 _를 사용하여 프라이빗한 폴더를 만들 수 있습니다. 이는 기존의 login 페이지와 모달의 컴포넌트가 동일하므로 공통 부분을 묶어줄 때 사용해주면 됩니다. 이또한 주소에 반영이 안됩니다.

이렇게 만들어주고 원래 로그인 page.tsx의 내용을 LoginModal.tsx로 옮겨준다음 원래 page.tsx의 내용을 변경해줍니다. 

import LoginModal from '@/app/(beforeLogin)/_component/LoginModal';

export default function LoginPage() {
  return <LoginModal />;
}

 

  • 주소창에 반영 안되는 폴더 규칙
    • (폴더명) : 그룹핑, 레이아웃용
    • @폴더명 : 병렬 라우트
    • _폴더명 : 프라이빗 폴더, 폴더 정리용

 

컴포넌트 import 규칙

넥스트에선 기본적으로 서버 컴포넌트가 클라이언트 컴포넌트를 import 해줄 순 있지만 그 반대인 클라이언트 컴포넌트가 서버 컴포넌트를 import 해주는 것은 안된다고 합니다. 그 이유는 후자의 경우 클라이언트 컴포넌트 아래의 서버 컴포넌트는 클라이언트 컴포넌트로 자동적으로 바뀌기 때문이라고 합니다. 이 부분이 사실 이해가 안되는데 차차 설명해주신다고 합니다.

 

useRouter

서버쪽 리다이렉트는 위에서 기재햇듯이 next/navigation 의 redirect 를 사용하면 되는데, 클라이언트쪽 리다이렉트는

'use client';

import { useRouter } from 'next/navigation';

export default function LoginPage() {
  const router = useRouter();
  router.replace('i/flow/login');
  return null;
}

useRouter 를 사용해줍니다.

useRouter 는 push와 replace 두 가지가 있고 페이지를 이동시키는 점은 동일하나 '뒤로가기' 이벤트를 실행했을 때 서로 다른 결과를 나타낸다고 합니다. push 의 경우 뒤로가기를 하면 바로 전의 페이지로 이동하지만 replace 의 경우 전전 페이지로 이동하는 차이점이 존재합니다.

 

 

섹션 1을 마치며

기본적인 구조 및 개념 설명을 마치고 섹션 2에선 클론코딩을 할 예정이라고 합니다. 여러 개념들이 호다닥 지나가서 사실 이해는 되어도 이걸 어떻게 적용할지 참 막막한데 그래도 공식 문서에서 보던걸 직접 적용해보니 이해가 더 잘 되는 것 같습니다. 완강까지 파이팅~