Next.js와 TypeScript

dev bourgeois·2023년 11월 18일
2

React A-Z Study

목록 보기
8/10
post-thumbnail

NextJS

React의 SSR(server side rendering)을 쉽게 구현할 수 있게 도와 주는 간단한 프레임워크이다. (리액트는 라이브러리)
리액트로 개발할 때 SPA(single Page Application)을 이용하며 CSR(Client Side Rendering)을 하기 때문에 좋은 점도 있지만 단점도 있는데 그 부분이 바로
검색엔진 최적화(SEO) 부분이다.
Client Side Rendering을 하면 첫페이지에서 빈 html을 가져와서 JS파일을 해석하여 화면을 구성하기 때문에 포털 검색에 거의 노출 될 일이 없다.

하지만 Next.js에서는 Pre-Rendering을 통해서 페이지를 미리 렌더링 하며 완성된 HTML을 가져오기 때문에 사용자와 검색 엔진 크롤러에게 바로 렌더링 된 페이지를 전달할 수 있게 된다.
리액트에서도 SSR을 지원하기면 구현하기에 굉장히 복잡하기 때문에 Next.js를 통해서 이 문제를 해결한다.

Server Side Rendering
-클라이언트 대신 서버에서 페이지를 준비하는 원리

  • 원래 리액트에서는 클라이언트 사이드 렌더링하기 때문에 서버에 영향을 미치지 않고, 서버에서 클라이언트로 응답해서 보낸 html도 거의 비어있다.

    ➡️ 이 방식은 서버에서 데이터를 가져올 때 지연 시간 발생으로 UX 측면에서 좋지 않을 수 있다.
    ➡️ 검색 엔진에 검색 시 웹크롤링이 동작할 때 내용을 제대로 가져와 읽을 수 없기에 검색엔진 최적화에 문제가 된다.

  • Next.js에서는 서버 사이드 렌더링을 이용하므로 사용자와 검색 엔진 크롤러에게 바로 렌더링된 페이지를 전달 할 수 있어서 검색엔진 최적화에 좋은 영향을 준다.

*설치 방법


NextJS 기본 파일 구조

pages

  • 이 폴더 안에 페이지들을 생성한다.
  • index.tsx가 처음 "/" 페이지로 된다.
  • _app.tsx 는 공통되는 레이아웃을 작성한다. 모든 페이지에 공통으로 들어가는 걸 넣어주려면 여기에 넣어주면 된다. (url을 통해 특정 페이지에 진입하기 전 통과하는 인터셉터이다.)
  • 만약 about이라는 페이지를 만드시려면 pages 폴더 안에 about.tsx를 생성해주시면 된다.

public

  • 이미지 같은 정적(static) 에셋들을 보관한다.

styles

  • 말 그래도 스타일링을 처리해주는 폴더
  • 모듈(module) css는 컴포넌트 종속적으로 스타일링하기 위한 것이며, 확장자 앞에 module을 붙여줘야 한다.

next.config.js

  • Nextjs는 웹팩을 기본 번들러로 사용한다.
  • 그래서 웹팩에 관한 설정들을 이 파일에서 해줄 수 있다.

Pre-rendering

NextJS는 모든 페이지를 pre-render 합니다.
이 pre-render한다는 의미는 모든 페이지를 위한 HTML을 Client사이드에서 자바스크립트로 처리하기 전, "사전에" 생성한다는 것이다.
이렇게 하기 때문에 SEO 검색엔진 최적화가 좋아진다.

Pre Render 테스트 !!!

✅ pre-render를 했기 때문에 NextJS 사이트에서 disable javascript를 해도 정상적으로 잘 나온다!


Summary


Data Fetching

Nextjs에서 데이터를 가져오는 방법

여러가지가 있다. 그래서 애플리케이션의 사용 용도에 따라서 다른 방법을 사용해주면 된다.
보통 리액트에서는 데이터를 가져올 때 useEffect안에서 가져온다.
하지만 Nextjs에서는 다른 방법을 사용해서 가져오는데 하나씩 봐보겠다.

📍getStaticProps

  • getStaticProps 함수를 async로 export 하면, getStaticProps에서 리턴되는 props를 가지고 페이지를 pre-render 한다. build time에 페이지를 렌더링 한다.

getStaticProps를 사용해야 할 때

  • 페이지를 렌더링하는 데 필요한 데이터는 사용자의 요청보다 먼저 build 시간에 필요한 데이터를 가져올 때
  • 데이터는 Headless CMS에서 데이터를 가져올 때
  • 데이터를 공개적으로 캐시할 수 있을 때(사용자별 아님).
  • 페이지는 미리 렌더링되어야 하고(SEO의 경우) 매우 빨리할 때(getStaticProps는 성능을 위해 CDN에서 캐시할 수 있는 HTML 및 JSON 파일을 생성한다.)

📍getStaticPaths

  • 동적 라우팅이 필요할 때 getStaticPaths로 경로 리스트를 정의하고, HTML에 build 시간에 렌더된다.
  • Nextjs는 pre-render에서 정적으로 getStaticPaths 에서 호출하는 경로들을 가져온다.

paths

  • 어떠한 경로가 pre-render 될지를 결정한다.
  • 만약 pages/posts/[id].js 이라는 이름의 동적 라우팅을 사용하는 페이지가 있다면 아래와 같이 된다.
  • 빌드하는 동안 /posts/1과 /posts/2를 생성하게 된다.

params

  • 페이지 이름이 pages/posts/[postId]/[commentId] 라면 , params은 postId와commentId 이다.
  • 만약 페이지 이름이 pages/[...slug] 와 같이 모든 경로를 사용한다면, params는 slug가 담긴 배열이어야한다. ['postId', 'commentId']

fallback

  • false 라면 getStaticPaths로 리턴되지 않는 것은 모두 404 페이지가 뜬다.
  • true 라면 getStaticPaths로 리턴되지 않는 것은 404로 뜨지 않고 , fallback 페이지가 뜬다.


📍getServerSideProps

  • getServerSideProps 함수를 async로 export 하면, Next는 각 요청마다 리턴되는 데이터를 getServerSideProps로 pre-render 한다.

getServerSideProps를 사용해야 할 때

  • 요청할 때 데이터를 가져와야하는 페이지를 미리 렌더해야 할 때 사용한다. 서버가 모든 요청에 대한 결과를 계산하고, 추가 구성없이 CDN에 의해 결과를 캐시할 수없기 때문에 첫번째 바이트까지의 시간은 getStaticProps보다 느리다.

Typescript

참조

TypeScript 가 나오게 된 배경

JavaScript는 원래 클라이언트 측 언어로 도입되었다.
그런데 Node.js의 개발로 인해서 JavaScript를 클라이언트 측 뿐만이 아닌 서버 측 기술로도 활용되게 만들었다.
그러나 JavaScript 코드가 커질수록 소스 코드가 더 복잡해져서 코드를 유지 관리하고 재사용하기가 어려워졌다.
더욱이 Type 검사 및 컴파일 시 오류 검사의 기능을 수용하지 못하기 때문에 JavaScript가 본격적인 서버 측 기술로 엔터프라이즈 수준에서 성공하지 못한다.
이 간극을 메우기 위해 TypeScript가 제시되었다.

TypeScript란
타입스크립트는 자바스크립트에 타입을 부여한 언어이다. 자바스크립트의 확장된 언어라고 볼 수 있다.
타입스크립트는 자바스크립트와 달리 브라우저에서 실행하려면 파일을 한번 변환해주어야 한다.
이 변환 과정을 우리는 컴파일(complile) 이라고 부른다.

Type System

  • 개발 환경에서 에러를 잡는 걸 도와줍니다.
  • type annotations를 사용해서 코드를 분석할 수 있습니다.
  • 오직 개발 환경에서만 활성화 됩니다.
  • 타입 스크립트와 성능 향상과는 관계가 없습니다.

TypeScript 사용하는 이유

  • ❇️TypeScript는 JavaScript 코드를 단순화하여 더 쉽게 읽고 디버그할 수 있도록 한다.
  • TypeScript는 오픈 소스이다.
  • TypeScript는 정적 검사와 같은 JavaScript IDE 및 사례를 위한 매우 생산적인 개발 도구를 제공한다.
  • TypeScript를 사용하면 코드를 더 쉽게 읽고 이해할 수 있다.
  • TypeScript를 사용하면 일반 JavaScript보다 크게 개선할 수 있다.
  • TypeScript는 ES6(ECMAScript 6)의 모든 이점과 더 많은 생산성을 제공한다.
  • TypeScript는 코드 유형 검사를 통해 JavaScript를 작성할 때 개발자가 일반적으로 겪는 고통스러운 버그를 피하는 데 도움이 될 수 있다.

Nextjs와 Typescript 만들 앱 소개


*간단한 블로그 앱
nextjs 공식 사이트 Documentation 에서 nextjs 를 배우기 위해 만드는 앱.
블로그 포스트 내용은 md 파일로 작성


메인 페이지 UI 만들기(마크다운 파일 생성)


*md 파일 안에 포스트 생성하기

Markdown은 텍스트 기반의 마크업언어로 쉽게 쓰고 읽을 수 있으며 HTML로 변환이 가능하다. 특수기호와 문자를 이용한 매우 간단한 구조의 문법을 사용하여 웹에서도 보다 빠르게 컨텐츠를 작성하고 보다 직관적으로 인식할 수 있다. 마크다운이 최근 각광받기 시작한 이유는 깃헙(https://github.com)에서 사용하는 README.md 덕분이다. 마크다운을 통해서 설치방법, 소스코드 설명, 이슈 등을 간단하게 기록하고 가독성을 높일 수 있다는 강점이 부각되면서 점점 여러 곳으로 퍼져가게 되고 있다.


*posts 폴더 생성

*파일 생성 후 마크다운 작성하기
그냥 위에 파일 복붙


마크다운 파일을 데이터로 추출하기


Typescript Type

In TypeScript,
a type is a convenient way to refer to the different properties
and functions that a value has.

타입이란, 그 value가 가지고 있는 프로퍼티나 함수를 추론할 수 있는 방법이다.

*Types in Typescript
TypeScript는 JavaScript에서 기본으로 제공하는 기본 제공 유형(built-in types)을 상속한다.
TypeScript 유형은 다음과 같이 분류된다.

  • Primitive Types
  • Object Types




Typescript 추가 제공 타입

참고

Any
애플리케이션을 만들 때, 잘 알지 못하는 타입을 표현해야 할 수가 있다. 이 값들은 사용자로부터 받은 데이터나 서드 파티 라이브러리 같은 동적인 컨텐츠에서 올 수도 있다. 이 경우 타입 검사를 하지 않고, 그 값들이 컴파일 시간에 검사를 통과하길 원한다.

➡️ 이를 위해,any 타입을 사용할 수 있다. 하지만 이 타입을 최대한 쓰지 않는게 좋다.
➡️ noImplicitAny 라는 옵션을 주면 any를 썻을 때 오류가 나오게 할 수 있다.


Union
TypeScript를 사용하면 변수 또는 함수 매개변수에 대해 둘 이상의 데이터 유형을 사용할 수 있다. 이것을 유니온 타입이라고 한다.


Tuple
TypeScript에서는 배열 타입을 보다 특수한 형태로 사용할 수 있는 tuple 타입을 지원한다. tuple에 명시적으로 지정된 형식에 따라 아이템 순서를 설정해야 되고, 추가되는 아이템 또한 tuple에 명시된 타입만 사용 가능하다.

*배열 Tuple

*Tuple에 요소 추가

*에러가 나는 경우

튜플은 'number | string'은 숫자와 문자열 값만 저장할 수 있다.

참고


Enum
enum은 enumerated type(열거형)을 의미한다.
Enum은 값들의 집합을 명명하고 이를 사용하도록 만든다.
여기서는 PrintMedia라 불리는 집합을 기억하기 어려운 숫자 대신 친숙한 이름으로 사용하기 위해 enum을 활용할 수 있다. 열거된 각 PrintMedia는 별도의 값이 설정되지 않은 경우 기본적으로 0부터 시작한다.

아래 코드에서 mediaType 변수에 할당된 값은 3이다. 설정된
PrintMedia 열거형 데이터의 Book 의 값이 숫자 3이기 때문이다.

enum 에 설정된 아이템에 값을 할당할 수도 있다.
값이 할당되지 않은 아이템은 이전 아이템의 값에 +1된 값이 설정된다.

아래 코드에서 mediaType 변수에 할당된 값은 56이다.
설정된 PrintMedia 열거형 데이터의 Book 의 값이 숫자 56이기 때문이다.

enum 타입의 편리한 기능으로 숫자 값을 통해 enum 값의 멤버 이름을 도출
할 수 있다.

또한 어떠한 언어 코드를 정의하는 코드를 작성할 때 언어의 집합을
만들 때도 enum을 사용 할 수 있다.

이렇게 enum을 이용해서 언어 집합을 만들어주면 어떠한 코드가 어떠한 나라의 언어 코드가 무엇인지 알지 못해도 쉽게 코드를 작성해 줄 수 있고 코드를 읽는 사람 입장에서도 가독성이 높아지게 된다.

이렇게 보면 enum과 JS의 object를 사용하는 것과 별 차이가 없어 보인다. 사실 enum은 그 자체로 객체이기도 하다.
➡️ Object.keys(LanguageCode) 를 하면 실제 키 값이 배열에 담겨 나온다.

=> ['korean', 'english']
Object.values(LanguageCode) 를 하면 value 값이 ...
=> ['ko', 'en']

enum과 객체의 차이점
object 는 코드내에서 새로운 속성을 자유롭게 추가할 수 있지만, enum 은 선언할 때 이후에 변경할 수 없다.
object 의 속성값은 JS가 허용하는 모든 타입이 올 수 있지만,
enum 의 속성값으로는 문자열 혹은 숫자만 허용된다.


Void
Java와 같은 언어와 유사하게 데이터가 없는 경우 void가 사용된다.
예를 들어, 함수가 값을 반환하지 않으면 반환 유형으로 void를 지정할 수 있다.
타입이 없는 상태이며, any 와 반대의 의미를 가진다.
void 소문자로 사용해야하며, 주로 함수의 리턴이 없을 때 사용해주면 된다.


Never

TypeScript는 절대 발생하지 않을 값을 나타내는 새 Type never를 도입했다.
Never 유형은 어떤 일이 절대 일어나지 않을 것이라고 확신할 때 사용된다.
일반적으로 함수의 리턴 타입으로 사용된다.
함수의 리턴 타입으로 never가 사용될 경우, 항상 오류를 리턴하거나 리턴 값을 절대로 내보내지 않음을 의미한다.
이는 무한 루프(loop)에 빠지는 것과 같습니다.

Void 와 Never의 차이
Void 유형은 값으로 undefind 이나 null 값을 가질 수 있으나,
Never 는 어떠한 값도 가질 수 없다.

TypeScript에서 값을 Return하지 않는 함수는 실제로 undefined를 반환한다.

위의 예에서 볼 수 있듯이 sayHi 함수는 반환 유형이 void인 경우에도 내부적으로 undefined를 반환하기 때문에 speech는 undefined 가 된다. Never 유형을 사용하는 경우 void는 Never에 할당할 수 없기 때문에 Speech:never는 컴파일 시간 오류를 발생시킨다.


type annotation, type inference

타입을 추론하지 못해서 타입 annotation을 꼭 해줘야하는 경우

📍 any 타입을 리턴하는 경우

coordinates에 hover해보면 const coordinates: any 라고 뜨는 것을 볼 수 있다. JSON.parse는 json을 파싱해준다. 인풋으로 들어가는 json을 확인하면 대충 어떤 타입이 리턴될지 개발자는 예상할 수 있지만, 타입스크립트는 여기까지 지원하지 않는다. 리턴 타입이 일정하지 않으므로 any를 리턴한다고 추론해버린다. 그러므로 이 경우에는 타입 애노테이션을 해주어야 한다.

📍 변수 선언을 먼저하고 나중에 초기화하는 경우

변수 선언과 동시에 초기화하면 타입을 추론하지만,
선언을 먼저하고 나중에 값을 초기화할 때에는 추론하지 못한다.
(any로 인식한다)

📍 변수에 대입될 값이 일정치 못하는 경우

여러 타입이 지정되어야 할 때에는 | (or statement) 로 여러 타입을 애노테이션 해준다.


type assertion

TypeScript에서는 시스템이 추론 및 분석한 타입 내용을 우리가 원하는 대로 얼마든지 바꿀 수 있다.
이때 "타입 표명(type assertion)"이라 불리는 메커니즘이 사용된다. TypeScript의 타입 표명은 프로그래머가 컴파일러에게 내가 너보다 타입에 더 잘 알고 있고, 나의 주장에 대해 의심하지 말라고 하는 것과 같다.
type assertion을 사용하면 값의 type을 설정하고 컴파일러에 이를 유추하지 않도록 지시할 수 있다. 이것은 프로그래머로서 TypeScript가 자체적으로 추론할 수 있는 것보다 변수 유형에 대해 더 잘 이해하고 있을 때이다.

컴파일러는 foo type이 속성이 없는 {}라고 가정하기 때문에 위의 예에서
는 컴파일러 오류가 발생한다.
그러나 아래와 같이 type assertion을 사용하면 이러한 상황을 피할 수 있다.

as Foo , <Foo>
타입 표명은 위에 두가지 방식으로 표현할 수 있다.
하지만 리액트를 사용할 때는 <Foo> 키워드는 JSX의 문법과 겹치기 때문에
as Foo를 공통적으로 사용하는게 추천됩니다.


getStaticProps를 이용한 포스트 리스트 나열

*빌드 타임에 포스트 자료 가져오기

*props으로 포스트 데이터 가져오기

*리스트 나열하기


포스트 자세히 보기 페이지로 이동(file system 기반의 라우팅)

파일기반 네비게이션 기능

리액트에서는 route를 위해서 react-router라는 라이브러리를 사용하지만 Next.js에는 페이지 개념을 기반으로 구축된 파일 시스템 기반 라우터가 있다. 파일이 페이지 디렉토리에 추가되면 자동으로 경로로 사용할 수 있다.
페이지 디렉토리 내의 파일은 가장 일반적인 패턴을 정의하는 데 사용할 수 있다.

파일 생성 예시

*포스트 파일 생성

*Link 함수를 이용한 페이지 이동

실행 결과

⚠️에러

next 13버전 이후부터 Link 태그 속에 a 태그를 직접 넣지 못하도록 변경되었다
➡️ legacyBehavior 속성을 사용하여 해결


포스트 데이터를 가져와서 보여주기(remark)

getStaticPaths

-동적 라우팅이 필요할 때 getStaticPaths로 경로 리스트를 정의하고, HTML에 build 시간에 렌더된다.
-Nextjs는 pre-render에서 정적으로 getStaticPaths 에서 호출하는 경로들을 가져온다.

*Post 데이터를 가져와야 하는 경로 목록을 가져오기

fallback

  • false 라면 getStaticPaths로 리턴되지 않는 것은 모두 404 페이지가 뜬다.
  • true 라면 getStaticPaths로 리턴되지 않는 것은 404로 뜨지 않고 , fallback 페이지가 뜨게 된다

*전달받은 아이디를 이용해서 해당 포스트의 데이터 가져오기

npm install remark remark-html --save

*가져온 데이터 화면에서 보여주기

코드에 HTML을 텍스트로 넣었을 때 안되는 이유는 XSS 공격을 막기 위해 React에서 일부러 해놓은 부분이다. 그러면 react에서 텍스트로 되어있는 HTML을 어떻게 보여줄 수 있을까?

*dangerouslySetInnerHTML
참고

dangerouslySetInnerHTML은 브라우저 DOM에서 innerHTML을 사용하기 위한 React의 대체 방법이다. 일반적으로 코드에서 HTML을 설정하는 것은 사이트 간 스크립팅 공격에 쉽게 노출될 수 있기 때문에 위험하다. 따라서 React에서 직접 HTML을 설정할 수는 있지만, 위험하다는 것을 상기시키기 위해 dangerouslySetInnerHTML을 작성하고 __html 키로 객체를 전달해야 한다.

*innerHTML

DOM 객체에는 innerHTML이란 속성이 존재합니다. 이속성은 DOM 요소에 대응되는 태그의 내부 HTML을 가리킵니다.


NextJS App Router


https://pocketbase.io/docs/

<사용법>







https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html
https://beta.nextjs.org/docs/rendering/server-and-client-components#server-components

*getPosts
https://beta.nextjs.org/docs/data-fetching/fetching

*에러처리
https://beta.nextjs.org/docs/data-fetching/fetching#asyncawait-in-server-components

*posts 나열하기

*PostItem 컴포넌트

*Static Data Fetching
https://beta.nextjs.org/docs/data-fetching/fetching#static-data-fetching

기본적으로 fetch 는 자동으로 데이터를 가져오고 캐시한다.

*Refresh on every request
https://beta.nextjs.org/docs/data-fetching/fetching#dynamic-data-fetching
캐시가 안되게 하고 모든 리퀘스트마다 다시 가져올 수 있게 해준다.

*데이터 가져오기

*Revalidating Data
https://beta.nextjs.org/docs/data-fetching/revalidating#background-revalidation

캐시된 데이터를 일정 시간 간격으로 재검증하려면 fetch()에서 next.revalidate 옵션을 사용할 수 있다. 기본 단위는 초이다.


처음 페이지에 들어온 이후에 10초 전에 다시 페이지를 리로드 하면
이미 데이터가 변경되어 있어도 캐시 된 데이터를 사용하게 된다.
그리고 10초 이후에 다시 리프레시를 하면 새로운 데이터를 보여준다.

*generateStaticParams
https://beta.nextjs.org/docs/api-reference/generate-static-params
generateStaticParams 함수는 해당 레이아웃 또는 페이지가 생성되기 전에 빌드 시간에 실행됩니다. Revalidation(ISR) 중에는 다시 호출되지 않는다.
getStaticPaths와 유사!

*id에 따른 Post 데이터

*UI 생성


*loading.tsx
https://beta.nextjs.org/docs/routing/loading-ui

Next.js 13에서는 React Suspense로 의미 있는 로딩 UI를 만드는 데 도움이 되는 새로운 파일 규칙 loading.js를 도입했다. 이 규칙을 사용하면 경로 세그먼트의 콘텐츠가 로드되는 동안 서버에서 즉시 로드 상태를 표시할 수 있으며 렌더링이 완료되면 새 콘텐츠가 자동으로 교체된다.

*error.tsx
https://beta.nextjs.org/docs/routing/error-handling

같은 폴더에서 error.js는 layout.js 및 template.js(있는 경우) 안에 중첩된다. page.js 파일과 그 아래의 모든 자식을 오류 경계로 래핑하지만 동일한 수준의 레이아웃이나 템플릿은 래핑하지 않는다.

*데이터 생성 컴포넌트 생성
https://beta.nextjs.org/docs/rendering/server-and-client-components#client-components

Client Component 를 사용하려면 앱 내부에 파일을 만들고 파일 상단에 "use client" 지시문을 추가한다(임포트하기 전에).

useState 또는 useEffect와 같은 클라이언트 후크를 사용하는 경우 구성 요소를 '클라이언트 사용'으로 표시하기만 하면 된다. 다른 클라이언트 구성 요소에서 가져오지 않을 때 자동으로 서버 구성 요소로 렌더링될 수 있도록 클라이언트 후크에 의존하지 않는 구성 요소를 지시문 없이 그대로 두는 것이 가장 좋다. 이를 통해 클라이언트측 JavaScript를 최소한으로 줄일 수 있다.

*POST 요청 보내기
https://beta.nextjs.org/docs/data-fetching/mutating

*CreatePost 컴포넌트 Import

*refresh()

refresh()를 호출하면 현재 경로가 서버에서 업데이트된 할일 목록을 새로고침하고 가져온다. 이는 브라우저 기록에 영향을 미치지 않지만 루트 레이아웃에서 아래로 데이터를 새로 고친다. refresh()를 사용할 때 React 및 브라우저 상태를 모두 포함하여 클라이언트 측 상태가 손실되지 않는다.
==> full page refresh를 안해도 된다.

1개의 댓글

comment-user-thumbnail
2024년 6월 26일

잘 읽고 갑니다

답글 달기