제로초님의 "Next + React Query로 SNS 서비스 만들기" 클론 코딩중

 

 

 

# 0. 클론 코딩 할 사이트의 구조를 파악

트위터 사이트를 클론할 것이니 트위터의 구조를 살펴보자.

 

  • 회원가입 팝업창, 로그인 팝업창 떴을 때 주소가 바뀐다.
  • 로그인 팝업 띄우고 새로고침 -> 뒷 배경 보임
  • 회원가입 팝업 띄우고 새로고침 -> 뒷 배경 안 보임
  • Infinite Scrolling
  • 왼쪽 메뉴(Navigation)
  • 탭 전환
  • 검색
  • 로딩창
  • 어떤 액션을 했을 때 주소가 바뀜 (Ex. 체크박스 on/off)
  • 게시하기 버튼 클릭은 어느 화면에서 가능 (대신 이것도 버튼 누르면 주소 바뀜)
  • 반응형 디자인

 

# 1. Next 프로젝트 생성

npx create-next-app@latest

 

근데 latest 버전으로 설치되니까 Next 15버전이 설치되었다..
별 생각없이 따라하다가 강의 화면과 다르게 나타나는 점이 있어 나중에 14버전으로 다시 설치했다.

 

 

# 2. Next 폴더 구조

public: next 서버에서 누구나 접근할 수 있게 serving 될 대상들

 

src: 개발 소스코드 폴더

ㄴ app: 라우팅 담당

app 폴더는 원래 src 바깥에 두는게 표준(?)인데
src 폴더를 가지게 되면 app 폴더에 포함되지 않는 이외의 소스코드를 app 폴더에 담지 않고 src 아래에 담을 수 있다. (취향차이)

 

next.config.js: next에 대한 설정

 

tsconfig.json: 타입스크립트 설정파일

 

 

# 3. path alias

@로 default 설정되어있음(@는 src를 가리킴)

'../../layout.tsx' -> @/app/layout.tsx 로 사용 가능

 

# 4. Next 프로젝트 실행

npm run dev

 

기본으로 3000 포트에서 돌아감

 

 

# 5. 폴더를 이용한 라우팅

기본 사용법

Next는 폴더 구조가 곧 URL 주소다.

 

app 폴더를 이용하면 라우팅을 쉽게 할 수 있다.

app 폴더 하위에 URL 체계 그대로 폴더를 생성해주면 된다.

http://localhost:3000/compose/tweet -> app/compose/tweet
http://localhost:3000/explore -> app/explore

 



동적 라우팅

근데 트위터에서

특정 사람의 게시글을 들어가면 {username}/status/{게시글ID} 이런 형태의 주소 체계를 가진다.

Next에서는 동적 라우팅 기능을 제공하며 대괄호를 이용하여 폴더를 생성하면 된다.

 

이 구조로 http://localhost:3000/elonmusk/status/121321321 로 접근하면?

-> elonmusk 유저의 게시글 ID가 121321321인 게시글 페이지가 된다.

 

 

# 6. 페이지

그리고 실제 페이지에 해당하는 파일은 page.tsx이다.

생성된 폴더들 하위에 page.tsx를 만들면 주소에 해당하는 페이지를 만들 수 있다.

 

 

 

 

참고로, 트위터에서 특정 유저의 개인페이지는 /{username}이라 [username] 폴더 하위에 page.tsx를 추가해주면 된다!

 

따라서,

[username]/page.tsx -> 특정 유저의 프로필 화면

[username]/status/[id]/page.tsx -> 특정 유저의 게시글 화면

이 되겠다.

 

 

그럼 만약 username이 이미 만들어진 폴더들(home, messages)과 같다면?? -> 정적 라우팅이 우선 적용된다.

username은 해당 폴더명으로 못 만들어지게 해야한다.

 

 

Not Found Page (404 페이지)

그리고 next에서는 not found page도 제공한다.

app/not-found.tsx를 생성하면 된다.

 

미리 만들어둔 페이지가 아닌 경우 나타난다.

 

 

 

 

# 6. Layout (레이아웃)

Root Layout (최상위 레이아웃)

src/app/layout.tsx

모든 페이지에 적용되는 루트 레이아웃이다.

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={`${geistSans.variable} ${geistMono.variable}`}>
        {children}
      </body>
    </html>
  );
}

 

페이지를 돌아다닐 때마다 page.tsx가 children으로 쏙 들어간다.

 

 

특정 페이지에 레이아웃 적용

만약 특정 페이지에서 layout을 따로 적용하고 싶다면?

page.tsx와 같은 레벨에 layout.tsx를 추가하면 된다.

export default async function HomeLayout(
    { children } : Readonly<{ children: React.ReactNode }>) {
        
    return <div>{children}</div>;
}

 

적용 순서는 이렇게 된다 RootLayout -> HomeLayout -> HomePage

 

 

 

 

Route Group 을 만들어 로그인 전 / 로그인 후 레이아웃 분리하기

트위터는 로그인 전 레이아웃과 로그인 후 레이아웃이 다르다.

이를 구현하기 위해 Route Group을 활용할 수 있다.

 

app 폴더 하위에 (afterLogin), (beforeLogin) 폴더 생성

=> 해당 폴더는 라우팅 기능이 없다!

 

그리고 (afterLogin)/layout.tsx, (beforeLogin)/layout.tsx를 만들어주면

Route Group에 해당하는 페이지들에게 공통된 레이아웃이 적용된다.

 

Next 서버를 실행시킨 상태로 (afterLogin), (beforeLogin) 폴더를 생성하거나 폴더를 이동하려니 알 수 없는 에러가 났다.. 서버를 껐다가 다시 켜주면 에러는 사라진다.
폴더 이동은 안전하게 서버를 끈 상태로 하자. (Next에서는 폴더 자체가 라우팅이니까?.?)

 

 

layout.tsx와 template.tsx

layout.tsx: 페이지가 바뀌어도 리렌더링 되지 않는다.

template.tsx: 페이지가 바뀌면 새롭게 마운트된다.

 

보통 layout.tsx를 쓰는데.. 언제 template.tsx 쓰나?

=> 페이지 넘나들때마다 무언갈 기록해야할 때 (Ex. 구글 애널리틱스 적용할 때?)

 

 

 

 

 

# 7. Next 내장 기능들

next/link

Next에서는 a 태그 대신 next/link를 사용한다.

=> a 태그를 사용하면 페이지가 새로고침 되면서 넘어가기 때문에 next/link를 사용하도록 하자.

 

next/image

이미지를 렌더링할 때엔 img 태그가 아닌 next/image를 사용한다.

장점은 알아서 최적화 해준다!

 

이미지 재료를 public 폴더에 넣어주고

import logo from 'path/logo' 로 불러와서 사용하면 된다.

 

 

 

 

# 8. CSS Module을 사용한 스타일링

스타일링 후보들

스타일링을 할 수 있는 방법은 다양하다.

 

tailwind -> 호불호 있고 가독성 안 좋음

emotion -> next 13이랑 잘 안맞음

styled component -> 서버 컴포넌트에서 문제 있음 (SSR 이슈)

vanilla extract -> windows와 문제있음ㅋㅋ... (SSR 문제는 없음)

css module -> 간단하다!

 

CSS Module

import styled from './page.module.css'

styles.{클래스명} 으로 사용

<div>
  <div className={styles.left}>
    <Image src={zLogo} alt="logo" />
  </div>
  <div className={styles.right}>
    <h1>지금 일어나고 있는 일</h1>
    <h2>지금 가입하세요.</h2>
    <Link href="/i/flow/signup" className={styles.signup}>
      계정 만들기
    </Link>
    <h3>이미 트위터에 가입하셨나요?</h3>
    <Link href="/login" className={styles.login}>
      로그인
    </Link>
  </div>
</div>

 

 

css module을 사용한 페이지에서 개발자도구를 켜면

클래스명 뒤에 요상한 문자들이 더 붙어있는 걸 볼 수 있다.


CSS Module을 사용하면 같은 클래스명이라도 다른 모듈로 인식되어 다르게 스타일이 적용될 수 있다.

 

 

Global CSS

전역 CSS는 global.css 파일을 사용

=> 전체 페이지에 적용될 공통 스타일은 여기에 작성

 

 

 

 

# 9. 라우팅 고급 기술 (여긴 너무 어렵다...)

Parallel Routes: 여러 개의 page.tsx를 동시에 보여주기

트위터의 로그인 팝업 화면의 주소는 시작 페이지(/) 주소와 다른데

시작 페이지가 뒤에 남아 있고 로그인 팝업창이 떠있다.

이런건 어떻게 구현할까?

 

parallel routes를 이용하면

모달창 구현 + history.back() 시 모달창이 닫힌 것처럼 동작하게 구현할 수 있다.

 

 

1. (beforeLogin) 폴더 하위에 @modal/page.tsx, layout.tsx, page.tsx 3개 파일 생성

@modal/page.tsx와 page.tsx는 같이 보여주고 싶은 페이지들

 

 

 

 

2. @modal/page.tsx

export default function Page() {
  return "패러랠 모달";
}

 

 

3. (beforeLogin)/page.tsx

=> 모달 뒤에 배경으로 둘 페이지

 

 

4. (beforeLogin)/layout.tsx

import { ReactNode } from "react";

type Props = {
  children: ReactNode;
  modal: ReactNode;
};
export default function Layout({ children, modal }: Props) {
  return (
    <div>
      비포 로그인 레이아웃
      {children}
      {modal}
    </div>
  );
}

 

여기서 children은 (beforeLogin)/page.tsx, modal은 @modal/page.tsx가 된다.

@modal이라고 폴더를 생성했기 때문에, layout에서도 modal로 뽑아내야 한다.

 

modal이 여러개면?
@modal2 폴더 만들고 layout에도 {modal2} 추가하면 된다.

 

 

 

 

 

그러나..

여기까지만 하게 되면 / 에 접근했을 때 모달도 같이 뜨게 된다.

 

@modal/default.tsx 파일을 추가하여

parallel routes가 필요없을 때 동작할 페이지를 정의한다. (parallel routes의 기본값 설정)

export default function Default() {
  return null;
}

 

 

따라서 위 layout.tsx 파일에서는

// 주소가 localhost:3000일 때는 children -> page.tsx, modal -> @modal/default.tsx
// 주소가 localhost:3000/i/flow/login일 때는 children -> i/flow/login/page.tsx, modal -> @modal/i/flow/login/page.tsx

 

폴더 최종 구조 ㅎㅎ;

 

 

 

Intercepting Routes: 라우팅 가로채기

<Link href="/i/flow/login" className={styles.login}>
  로그인
</Link>

 

여기서 Link를 누르면 app/i/flow/login/page.tsx의 내용이 나타난다.

@modal 하위에 있는 page로 인터셉트 하려면

 

(.)i 폴더를 만든 후 그대로 flow/login을 만든다..

(.)->현재 (..)->부모를 할 수 있는데 브라우저 주소 기준으로 작성해야한다.

 

(beforeLogin)과 @modal은 주소에 반영되지 않으므로 (.)i로 표시

 

Parallel Routes와 Intercepting Routes를 사용하여 한 화면에서 모달 띄우기를 완성했다..

라우팅을 가로채고 -> 가로챈 라우팅을 모달로 띄우라...

와 진짜 어렵다

 

근데???

/i/flow/login에서 새로고침하면 @modal 아래에 인터셉트 라우팅이 적용되지 않는다....

클라이언트에서 라우팅할 때만 인터셉트 라우팅이 적용된다....

 

진짜 어려워....

 

 

 

 

Private folder

폴더명 앞에 _(언더바)를 붙인 형태

내부 쓰임용? 폴더다. 공통 파일 정리할 때 사용한다.

 

 

 

 

주소에 영향이 없는 폴더들

@parallel routes

(라우팅그룹)

(..) (.)

intercepting routes,

_private folder

 

 

 

 

# 10. 기타

서버 컴포넌트, 클라이언트 컴포넌트

React 18에 도입된 기능으로써 서버 컴포넌트라는 개념이 생겼다.

 

특징

- 서버 컴포넌트이기 때문에 기본적으로 서버가 사용할 수 있는 기능들을 써먹을 수 있다.

- Next 서버에서 돌아간다.

- 주로 데이터와 관련하여 쓰임

 

기본적으로 page.tsx 파일은 서버 컴포넌트가 된다.

 

하지만, 서버 컴포넌트에서는 useState나 useEffect와 같은 hook을 사용하지 못한다.

hook을 사용하기 위해 클라이언트 컴포넌트로 만들어줘야한다!

=> 파일 상단에 'use client' 추가

 

 

 

Page Redirect

page redirect

트위터 페이지에서 로그인 버튼을 누르면 /login -> /i/flow/login으로 리다이렉트 되는 걸 볼 수 있다.

 

우선 app/(beforeLogin)/login/page.tsx 만들고

next/navigation에서 제공하는 redirect를 사용한다.

import { redirect } from "next/navigation";

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

 

 

 

 

 

+ Recent posts