2차 공부/TIL

24.08.26 Next JS Data Fetching / loading

공대탈출 2024. 8. 26. 10:28
import { useEffect, useState } from "react";

export const metadata = {
    title: "Home",
};

export default function App() {
    const [movies, setMovies] = useState();
    const getMovies = async () => {
        const response = await fetch(process.env.MOVIE_GET_URL);
        const json = await response.json();
        setMovies(json);
    };
    useEffect(() => {
        getMovies();
    }, []);
    return <div>{JSON.stringify(movies)}</div>;
}

리액트에서 했던것처럼 async await, fetch, .json()을 사용하여 state에 받아온 데이터를 저장하고, 화면에 보여주는 코드이다.

NextJS는 바로 오류를 뱉어버린다. useEffect와 같은 hook은 클라이언트 컴포넌트에서만 사용이 가능하기 때문에 use client를 코드에 작성해 클라이언트 컴포넌트로 만들라는 오류이다.

 

use client를 붙이고 작동하면 오류가 발생하지 않을까?

"use client";
import { useEffect, useState } from "react";

export const metadata = {
    title: "Home",
};

export default function App() {
    const [movies, setMovies] = useState();
    const getMovies = async () => {
        const response = await fetch(process.env.MOVIE_GET_URL);
        const json = await response.json();
        setMovies(json);
    };
    useEffect(() => {
        getMovies();
    }, []);
    return <div>{JSON.stringify(movies)}</div>;
}

오류를 뱉어낸다. metadata를 use client로 표시한 컴포넌트에서 export하려하고있다는 오류였다. 

서버 컴포넌트에서만 metadata를 export할 수 있기 때문에 metadata를 지워야 한다.

 

"use client";
import { useEffect, useState } from "react";

export default function App() {
    const [movies, setMovies] = useState();
    const getMovies = async () => {
        const response = await fetch(process.env.MOVIE_GET_URL);
        const json = await response.json();
        setMovies(json);
    };
    useEffect(() => {
        getMovies();
    }, []);
    return <div>{JSON.stringify(movies)}</div>;
}

 

 

하지만 오류가 발생한다. 이유는 요청 url이 올바르지 않기 때문이다.

 

찾아보니 Next JS에서 env를 사용해 데이터를 불러오는 작업을 할 때에는 env 상수 앞에 NEXT_PUBLIC_ 을 붙여야 사용가능하다고 한다.

 따라서 상수명과 불러오는 곳을 수정해보면

정상적인 데이터 fetching이 이뤄지는 것을 볼 수 있다.

 

"use client";
import { useEffect, useState } from "react";

export default function App() {
    const [isLoading, setIsLoading] = useState(true);
    const [movies, setMovies] = useState([]);
    const getMovies = async () => {
        const response = await fetch(process.env.NEXT_PUBLIC_MOVIE_GET_URL);
        const json = await response.json();
        setMovies(json);
        setIsLoading(false);
    };
    useEffect(() => {
        getMovies();
    }, []);
    return <div>{isLoading ? "Loading" : JSON.stringify(movies)}</div>;
}

이렇게 isLoading상태를 만들어 로딩중에 표시를 할 수 있도록 설정했다.

 

하지만 서버컴포넌트에서 fetching을 실행해 데이터 흐름의 위험을 줄일 수 있다면? useEffect와 useState를 사용하지 않아도 된다면?


export const metadata = {
    title: "Home",
};

const URL = "https://api.url.com";

async function getMovies() {
    const response = await fetch(URL);
    const json = await response.json();
    return json;
}

export default async function HomePage() {
    const movies = await getMovies();
    return <div>{JSON.stringify(movies)}</div>;
}

use client를 삭제하고 getMovies를 컴포넌트 밖으로 빼주었다.

그리고 함수형 컴포넌트 자체에 async를 붙여주면 프론트엔드 배포서버(현재는로컬서버)에서 fetching을 한 뒤 사용자에게 페이지를 보여주게 된다.

유저(브라우저)가 요청 데이터를 찾아볼 수 없다.

async function getMovies() {
    await new Promise(resolve=>setTimeout(resolve, 5000))
    const response = await fetch(URL);
    const json = await response.json();
    return json;
}

getMovies를 이렇게 Promise를 사용하여 5초 기다린 뒤 fetching을 실행하게 해보자.

만약 클라이언트에서 데이터 fetching이 이뤄진다면, 5초를 기다리지 않고 페이지가 로드될 것이다.

서버측에서 await함수가 다 끝나고난 뒤 사용자에게 페이지를 보여주는 것이다.

 

하지만, 한번 fetching한 뒤 여러 페이지를 라우팅하고 다시 페이지로 돌아오거나, 새로고침을 하더라도 5초를 기다리지 않게 된다.

그 이유는 NextJS가 처음 fetching한 데이터를 캐싱해두기 때문이다. 즉, 그 사이에 서버측에서 데이터가 바뀌었든 말든 상관없이 처음 fetching한 데이터를 보여주기 때문에 페이지 이동후 돌아왔을 때 빠르게 볼 수 있는 것이다.

처음 데이터를 받아올때는 오래걸리더라도, 그 후는 캐싱된 데이터를 보여주어 빠른 페이지 이동을 할 수 있다.

하지만, 사용자에게 통신이 오래걸릴동안 페이지이동이 되지 않는 것은 UX적인 측면에서 올바르지 않다고 생각한다.

그걸 NextJS에서 제공하는 loading component로 해결할 수 있다.


이렇게 로딩이 뜨길 원하는 곳에 loading.tsx(jsx)를 만들어준다.

 

컴포넌트가 async로 만들어져있기 때문에 await가 끝나고 return이 들어오기 전까지

브라우저는 NextJS 백엔드가 제공한 loading컴포넌트페이지를 보여준다. 그리고 await가 끝나고 브라우저로 return이 들어오면 해당 페이지를 보여주는 것이다.