[TIL] Next.js(2)

jeongjwon·2023년 12월 8일
0

이론

목록 보기
19/19

SPA

앞서 포스팅했던 블로그에서 Next.js 의 큰 장점은 SSR 방식으로 렌더링한다는 것이었다. 이와 더불어 SPA 라는 장점이 있다. SPA 가 무엇인지 예를 들어 설명해보겠다.

어떤 페이지에서 a 태그를 이용하여 클릭했을 때 다른 페이지로 이동할 수 있다.
이때 컨텐츠가 바뀌는 곳이 전체가 아니라 아주 작은 부분이라도 웹페이지는 전체 코드를 업데이트하고 다운로드한다.
이는 사용자입장에서는 느린 속도를 경험하고, 서비스를 제공하는 입장에서는 비용이 크다는 문제가 있다.

a 태그 대신 Link 태그를 이용한다면 문제를 해결할 수 있다.
아직 클릭하지 않고 hover 만 해도 이미 background 에서 그 파일을 다운로드 했기 때문에 빠른 속도와 비용을 절약할 수 있다.

이렇게 페이지가 여러 개이지만 마치 한 페이지처럼 동작하는 어플리케이션을 SPA (Single Page Application) 이라고 한다. 동적으로 바뀐 데이터만 바뀌게 되는 로직이다. 장점만 있는 것은 아니다. 초기에 모든 파일을 다운로드해야하기 때문에 초기에 렌더링되는 속도에서는 조금 느리게 느껴질 수도 있다.




Routing

라우팅이란 경로에 대해 컨텐츠를 어떻게 보여줄지에 대한 방법을 나타낸다.

http://a.com/dashboard/analytics/
라는 주소에서
a.comdomain 을 가리키고, 각각의 dashboardanalyticssegment 로 분류할 수 있다.
그리고 segment들을 합친 /dashboard/analytics/path 라고 부른다.


중첩 라우팅

nextjs 에서 대표적으로 layout.js 과 page.js 가 있다.
부분적인 파일은 page.js 에서 진행되고 큰 틀이나 공통적인 내용은 layout.js 에서 진행된다.
홈페이지는 app 폴더 내 page.js 의 리턴값은 layout.js 의 children에 결합해서 만들어진 최종적인 html 을 응답한다.
app 폴더 내 또 다른 라우터 폴더 안의 page.js 는 부모라고 할 수 있는 layout.js 에게 맡겨진다.

말이 조금 어렵지만, Next.js는 URL 경로의 세그먼트에 따라 콘텐츠를 찾고, 해당 콘텐츠가 위치한 폴더의 layout.js를 시작으로 상위 폴더를 탐색하면서 layout.js로 감싸주는 중첩된 layout 구조를 가지고 있다고 생각하면 된다.


Dynamic 라우팅

예를 들어 /read/1/ 과 /read/2/ 와 같이 1과 2 는 가변적으로 제공되는 segment 들이다. 이런 경로를 처리하기 위해서는 Dynamic Routing 을 사용한다.
app 폴더 내 read 라우팅 폴더 내 변경될 수 있는 값(1,2,,,) [id] 폴더를 생성하여 이 폴더 안에서 layout.js 이나 page.js 로 콘텐츠를 만들어주면 된다. (app/read/[id]/page.js )


이런 방식으로 Next.js는 간단하고 직관적인 라우팅을 제공하며, 프로젝트의 복잡성을 효과적으로 관리할 수 있도록 도와준다.




BackEnd

백엔드에서 보통 동적으로 데이터를 가져온다. nextjs 에서는 직접 API를 구축할 수 있도록 문서에 제공한다. 이 강의에서는 json서버로 빠르게 서버를 구축하도록 하였다.

npx json-server --port 9999 --watch db.json
9999번 포트에 db.json 라는 폴더 내에 저장된 데이터들을 watch 하면서 서버와 통신하게끔 하겠다는 의미이다.

명령어를 터미널에 입력하면 루트 폴더 내에 db.json 폴더가 생성된다.

기존 파일 내에는 posts, comments, profile 과 같은 속성이 저장되어 있고, 별도의 속성을 만들고자 하면, 위와 같은 형식으로 만들어주면 된다.

"topics": [
  {
  	"id": 1, "title": "html", "body": "html is ...",
  },
  {
  	"id": 2, "title": "css", "body": "css is ...",
  },
]

만들어준 데이터를 가져오기 위해서 개발자 도구의 콘솔 창에서 해당 서버 주소를 fetch 하여 받은 response 를 json 형태로 컨버팅하여 출력하면 임의로 넣어준 배열값이 출력된 것을 볼 수 있다.

서버 리소스데이터 불러오기






Server Component vs Client Component

next.js 의 컴포넌트는 크게 server component 와 client component 로 나눌 수 있다. 두 컴포넌트는 공통적으로 정보를 표현하지만 사용자와의 상호작용이 있냐 없냐에 따라 없다면 server component 로, 있다면 client component 로 분류할 수 있다.

이렇게 나눈 이유를 살펴보자.

서버와의 통신에 사용되는 useEffect 훅을 비롯한 javascript 는 실행되지 않는다. 컨텐츠들은 화면에 표시되지 않을 뿐만 아니라 id 나 password 같은 보안정보를 유출시킬 수 있는 위험이 있다.

server 쪽에서 비동기적으로 fetch 메서드를 호출하고 실행이 될 때까지 기다린 후에 결과값을 json 으로 변경시키고 데이터를 가져오면 동적으로 생성한 다음에 그 결과를 .next 폴더에 저장한 후에 최종 정적인 파일만 client 측에 전달하면 콘텐츠를 보내줄 수 있다.

이렇게 각자의 할 일들을 서버와 클라이언트로 나누어 주면 다음과 같은 이점이 생긴다.

  • 간결한 코드 : useEffect 나 useState 훅들을 사용하지 않고 훨씬 간결하게 코드를 사용해 이해하기도 쉽고 유지보수도 쉬워 버그도 줄어들게 된다.
  • 빠른 데이터 액세스 : 큰 리소스에 접근해야할 경우, 서버 컴포넌트는 서버와 데이터베이스가 가까운 위치에서 동작하기 때문에 훨씬 빠른 속도로 접근할 수 있다.
  • 보안 : 서버컴포넌트는 클라이언트에 민감한 정보를 전송하지 않기때문에 안전하게 처리하고 보안을 유지할 수 있다.
  • 성능 향상 : 서버 컴포너느는 클라이언트로 javascript 코드를 전송하지 않기 떄문에 전송되는 데이터 양도 줄어들고 부하도 줄어듦으로써 전반적으로 성능을 향상시키는 데 도움이 되고 javascript 를 쓸 수 없는 환경에서도 잘 동작할 수 있다.



코드

  1. 기본적으로 use client 라는 코드가 상단에 없으면 서버 컴포넌트를 의미한다. 있다면 클라이언트 컴포넌트를 뜻한다.

  1. nodejs 13version 이후로 라우터가 page router 에서 app router 로 변경됨에 따라 import 위치도 next/router 에서 next/navigation 으로 변경되었다.

  2. 자식 컴포넌트에게 props 를 전달해주어 props.params.id 를 이용해 id 값을 얻을 수 있지만, 부모 컴포넌트에게 받지 않는 경우에는 useParams() 훅을 이용해 parameter 를 읽어올 수 있다.

예를 들어 http://localhost:3000/read/1이라는 주소에서 read/1 은 path 를 의미한다. 뒤의 1이라는 segment 는 redirect 할 때 id 값으로 명시해놓은 것이기 때문에 parmas 를 출력하면 {id:1} 이라는 값으로 얻은 id 에 접근할 수 있다.

props 이용useParams() 이용
const params = useParams();
console.log(parmas);  // {id:1}
const id = params.id;
console.log(id); // 1

  1. CRUD
  • create
    • input 태그와 textarea 태그를 이용해 title 과 body 를 입력받는다. ➡️ 사용자와 상호작용이 존재하기 때문에 이 컴포넌트는 클라이언트 컴포넌트이다.
    • submit 을 하면 POST 요청을 한다.
    • 요청이 성공적이라면 결과값의 id 값을 이용해 페이지 리로드 없이 해당 페이지로 이동한다. ( router.push('/read/${topic.id}');)
    • 서버 컴포넌트를 서버 쪽에서 다시 렌더링해서 새로고침 한다. (router.refresh())
  • read
    • fetch 메서드 기본값인 get 요청을 통해 성공적이라면 결과값을 json convert 하여 읽어올 수 있다. ➡️ 사용자와의 상호작용이 없기 때문에 서버 컴포넌트이다.
  • update
    • create 와 동일하게 UI는 비슷하다. 기존의 값을 변경시키기 위함(클라이언트 컴포넌트)이기 때문에 첫 mount 시, 기본 데이터를 먼저 get 요청을 통해 성공시 input과 textarea의 value값인 title과 body 값을 설정해준다.
    • 사용자의 입력을 받아 submit 하면 PATCH 나 PUT 요청을 한다.
    • 성공적이라면 결과값의 id 값을 이용해 리다이렉트와 새로고침한다.
  • delete
    • delete 버튼을 클릭시에는 DELETE 요청을 통해 성공적이라면 루트로 리다이렉트와 새로고침을 한다. ➡️ 클릭 상호작용이 필요하기 때문에 클라이언트 컴포넌트이다.

read 를 제외하고 create, update, delete 는 사용자의 입력이나 클릭을 통해 이루어지기 때문에 클라이언트 컴포넌트로 수행한다. read 는 그저 서버로부터 데이터를 읽어오는 것이기 때문에 비동기적으로 수행가능하다.

공통적으로 fetch 메서드를 이용해 get, post, patch, delete 요청 후 router.push(리다이렉트될 주소) , router.refresh() 를 통해 해당 페이지로 이동 후 새로고침을 하도록 한다.

새로고침을 했음에도 불구하고, 데이터가 갱신되지 않을 수도 있다. 이는 처음에 존재하지 않았던 MISS 상태였던 cache 이후 이미 사용했기 때문에 HIT 상태라 변경이 되지 않음을 의미한다. 다시말해 캐쉬 기능이 켜져 있기 때문이다.
이를 해결하기 위해서는 캐쉬 기능을 꺼야하므로, 요청시 {cache: "no-cache"}라는 것이 방법이다.


  1. 보안을 위한 환경 변수 설정
    요청시 주소를 보면 하드코딩을 한 흔적들이 존재한다. 이런 주소나 사용자의 중요한 정보등을 유출시키면 보안에도 아주 취약하다. 이 때문에 루트에서 .env.local 폴더를 생성해 공통적으로 사용할 환경변수를 설정한다.
//.env.local
API_URL=http://localhost:9999/

로 설정후 사용할 파일에서는 props.env.API_URL 과 같이 사용할 수 있다.
하지만 클라이언트 컴포넌트에서는 다른 방법으로 사용해야한다.

공식문서에서는 NEXT_PUBLIC_ 이라는 접두어를 사용해서 환경 변수를 설정하고, 사용할 때에도 props.env.NEXT_PUBLIC_API_URL 로 사용해야한다고 한다.




마치며

긴 강의인줄 알았는데 짧은 시간동안 짧고 굵게 nextjs 의 기능을 훑어볼 수 있었다. 꽤나 흥미로웠던 점은 컴포넌트가 클라이언트 컴포넌트와 서버 컴포넌트로 분리되어 사용된다는 점이다. 이는 분명 사용자 입장에서의 빠른 속도와 개발자 입장에서도 효율적으로 리소스를 활용할 수 있고 비용을 줄일 수 있다는 분명한 장점이 있어서 놀라웠다.
이번 강의에서는 json 을 통해 서버를 구축하는 방법을 배웠지만 직접 API를 구축하는 방법도 살펴보고 싶다.


0개의 댓글