2024.03.14 TIL - 트러블슈팅(SSG 데이터 가져오기 오류, 구조분해할당 오류, CSR map오류, useRouter 오류), Dynamic Route Segments(route handler로 Delete, toggle하기), next.js에서 이미지넣기

Innes·2024년 3월 14일
0

TIL(Today I Learned)

목록 보기
89/147
post-thumbnail

🏹 트러블슈팅

SSG 데이터 가져오기 오류

  • 문제 1. 내부 서버에서 json-server의 db를 가져오고, 내부 서버에서 클라이언트로 데이터를 반환해서 전달해주는 로직을 짰는데, db를 수정했는데도 자꾸 수정 전 데이터가 들어왔다.

  • 원인

    • cache: force-cache 옵션으로 이전 값이 캐싱되어 있음
      -> .next라는 폴더에 캐싱된 값이 들어있으므로 해당 폴더를 지워주자
      (다음 렌더링때 그 시점의 값이 캐싱되면서 어차피 다시 생김)
    • 컴포넌트에서 SSG로 구현하고 싶으면, 내부 서버가 아니라 json-server에 바로 통신해서 data를 가져와야 했던 것이었음

      Why?!

      • 빌드될 때 내부 서버, 즉 route.ts도 page.tsx와 같은 시점에 렌더링되기 때문에 page.tsx에서 짜놓은 내부 서버 통신 로직에서 해당 서버가 없는 상태가 발생한다. (걔도 아직 렌더링되고 있는 상태니까)
      • 내부서버(ex : localhost:3000/api) 에 접근할 때의 시점에 해당 api가 준비되지 않아 발생하는 문제!
      • 따라서, SSG로 구현하고 싶을때는 기존에 리액트 코드 짤때처럼 json-server에 곧장 통신하는 방식을 써야 한다.
  • 문제 2. 구조분해할당해서 name, description, image를 가져왔는데 console에 찍을때는 잘 들어오면서 막상 브라우저에서는 description은 undefined에, image도 db수정전 데이터가 들어왔다.

  • 원인

    • 가져온 response.json()은 네이밍되어있지 않은 객체임. key: {객체} 형식이어야 companyInfo라는 key를 갖고 구조분해할당을 할텐데, 없는 companyInfo를 찾고있는거였음.
      -> 객체는 통으로 가져오고, 그 객체를 구조분해할당 하는게 맞는거였다.
// ❌ 기존

import React from "react";

const AboutPage = async () => {
  const response = await fetch(`http://localhost:4005/companyInfo`);

  // 여기서 구조분해할당을 잘못하고있다!
  const { companyInfo } = await response.json();
  console.log(companyInfo);

  const { name, description, image } = companyInfo;

  return (
    <div>
      <li>{name}</li>
      <li>{description}</li>
      <img src={image} alt="" width={400} height={400} />
    </div>
  );
};

export default AboutPage;

// ✅ 해결

import React from "react";

const AboutPage = async () => {
  const response = await fetch(`http://localhost:4005/companyInfo`);

  // 이렇게 통으로 가져와서
  const data = await response.json();
  console.log(data);

  // 여기서 구조분해할당 하는게 맞다.
  const { name, description, image } = data;

  return (
    <div>
      <li>{name}</li>
      <li>{description}</li>
      <img src={image} alt="" width={400} height={400} />
    </div>
  );
};

export default AboutPage;

CSR map 오류

  • 문제 : 내부 서버에서 json-server db의 todos를 가져오고, 그 todos 배열을 map으로 돌리려고 했는데 void[] 형식은 ReactNode에 할당할 수 없습니다 라는 오류메시지 발생!

  • 원인 : map의 return문에 아무것도 없어서... 생긴 오류였음....... todo도 넣어보고 값 넣어보긴 했는데 태그를 넣어보진 않았다 ㅠㅠ

  • 해결 : <></>만 넣었더니 바로 해결됨


useRouter 오류

  • 오류 메시지 : Unhandled Runtime Error Error: NextRouter was not mounted. https://nextjs.org/docs/messages/next-router-not-mounted

  • 문제 : 버튼 클릭시 페이지 이동시키고싶어서 useRouter를 사용했는데 계속 runtime error 발생

  • 원인 : useRouternext/router에서 import❌, next/navigation에서 import✅


Dynamic Route Segments

🧡 Route Handler를 통해 특정 id의 todo를 delete, toggle하고싶어서 찾던 중 알게된 방식!

  1. Dynamic Route Segments를 사용하지 않고 delete했던 첫번째 시도
  • 로직 : 내부 서버(route)로 request 보낼때 bodyid를 담아서 보내기

    • http://localhost:3000/api/todos/${id}도 시도해보았지만, 404 Not Found가 뜸
      -> 잘못된 방식이라 생각해서 id없는 todos url(http://localhost:3000/api/todos)로 request를 보내려다보니, 그럼 id는 어떻게 보내야하지..? 생각하던 중 착안한 방식
  • 코드

// ✅ Csr.tsx - todoList(next.js)

"use client";

//...생략
const { mutate: deleteTodo } = useMutation({
    mutationFn: async (id) => {
      // url로 id를 전달하지 않음
      await fetch(`http://localhost:3000/api/todos`, {
        method: "DELETE",
        headers: {
          "Content-Type": "application/json",
        },
        // body로 id를 전달함
        body: JSON.stringify({ id }),
      });
    },
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ["todos"],
      });
    },
  });

const onDeleteHandler = (id: string) => {
    deleteTodo(id);
  };
// ✅ route.ts (app > api > todos)

export async function DELETE(request: Request) {
  
  // request body로 가져온 id 뽑아쓰기
  const { id } = await request.json();
  const response = await 
  // 가져온 id를 url 뒤에 넣어 해당 todo를 delete!
  fetch(`http://localhost:4005/todos/${id}`, {
    method: "DELETE",
  });
  return Response.json({
    id,
  });
}
  • 이 방법도 성공하긴 했지만, 정석적인 방법은 아니었다.
    Dynamic Route Segments를 활용하여, 굳이 body에 id를 담아 보내지 않더라도 쉽게 id를 보낼 수 있다.
  1. Dynamic Route Segements로 delete 구현하기
  • todos 폴더 안에 [id]폴더 생성 -> [id]폴더 안에 route.ts파일 생성하기
    -> 해당 route.ts 파일에서 ⭐️params를 활용하여 id를 가져올 수 있다!

👍🏻 공식문서 : Next.js - Dynamic Route Segments

  • 코드
// ✅ Csr.tsx - TodoList(next.js)

// ...생략
const { mutate: deleteTodo } = useMutation({
    mutationFn: async (id: string) => {
      await fetch(`http://localhost:3000/api/todos/${id}`, {
        method: "DELETE",
      });
    },
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ["todos"],
      });
    },
  });

const onDeleteHandler = (id: string) => {
    deleteTodo(id);
  };
// ✅ route.ts (app > todos > ⭐️ [id])

export async function DELETE(
  request: Request,
   // ⭐️ params도 가져오기!
  { params }: { params: { id: string } }
) {
  // ⭐️ params에서 id를 빼서 쓴다!
  const id = params.id;
  const response = await fetch(`http://localhost:4005/todos/${id}`, {
    method: "DELETE",
  });
  return Response.json({
    id,
  });
}
  • ⭐️ 마찬가지 방법으로 todo의 isDone상태를 바꿔보자! (toggle)
// ✅ Csr.tsx - todoList(next.js)

// ...생략
const { mutate: toggleTodo } = useMutation({
    // 가져온 id는 url뒤에 넣어주고, isDone은 body통해 route.ts에 보낼 예정
    mutationFn: async ({ id, isDone }: { id: string; isDone: boolean }) => {
      await fetch(`http://localhost:3000/api/todos/${id}`, {
        // isDone의 상태를 수정할거라 PATCH
        method: "PATCH",
        headers: {
          "Content-Type": "application/json",
        },
        // isDone을 body통해 내부서버(route.ts)로 보내주기
        body: JSON.stringify({ isDone }),
      });
    },
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ["todos"],
      });
    },
  });

  // '완료'버튼 클릭시 mutaion함수로 id, isDone을 전달함
  const onToggleHandler = (id: string, isDone: boolean) => {
    toggleTodo({ id, isDone });
  };
// route.ts (app > todos > ⭐️ [id])

export async function PATCH(
  request: Request,
   // delete와 마찬가지로 params를 가져옴
  { params }: { params: { id: string } }
) {
  
  // body통해 받아온 isDone을 뽑아주기
  const { isDone } = await request.json();
  // url뒤에 넣을 id도 선언
  const id = params.id;

  // 해당 id의 url로, body통해 isDone 상태를 !isDone으로 수정하겠다는 요청 보내기
  const response = await fetch(`http://localhost:4005/todos/${id}`, {
    method: "PATCH",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ isDone: !isDone }),
  });

  // return값이 없으면 500 내부서버 오류가 발생한다. 
  return Response.json({
    id,
    isDone,
  });
}

Next.js에서 이미지넣기

세상 간단함... 이미지를 Public폴더에 넣은다음, img src="" 여기에 그냥 파일명 넣어주면 끝..!!

profile
꾸준히 성장하는 우상향 개발자

0개의 댓글