akjfal

Next13 notFound()에 대해 알아보자 본문

하루의 이슈

Next13 notFound()에 대해 알아보자

akjfal 2023. 5. 15. 17:31

next.js github discussion에 질문을 올린 내용입니다.

https://github.com/vercel/next.js/discussions/49433

notFound()를 사용하면서 이슈를 해결하기 위해 테스트하다보니 여러 케이스를 발견하게 되었습니다. 상세한 로직은 해당 github를 참조하시기 바랍니다. https://github.com/osydoo/next13-not-found

Summary

  1. ‘use client’의 useEffect의 동작이 의존성에 따라서 다르게 동작하는 이유가 뭔가요?
  2. axios.interceptors를 동기적으로 실행했을 때는 notFound()가 에러를 뱉고, 비동기로 했을때는 동작이 무시되는 이유가 뭔가요?
  3. 클릭이벤트에선 왜 에러를 뱉나요?

Case

In 'use client'

useEffect - Error case

useEffect(()=> {
    (async () => {
        try{
            await axios.get('<http://127.0.0.1:3001/error>'); // 404 error
            console.log('success');
        }catch(e){
            console.log('error')
            notFound()
        }
    })()
}, [])

 

useEffect - useEffect의 의존성이 [] 일땐 동작안하고 여긴 동작하는 이유가 뭔가요?

const [data, setData] = useState('init');

useEffect(()=> {
    (async () => {
        try{
            await axios.get('<http://127.0.0.1:3001/error>'); // 404 error
            console.log('success');
        }catch(e){
            console.log('error')
            setData('error')
        }
    })()
}, [])

useEffect(()=> {
    if(data === 'error') {
        notFound();
    }    
}, [data])

 

click event - Error case

const handleClick = async () => {
        try{
            await axios.get('<http://127.0.0.1:3001/error>'); // 404 error
            console.log('success');
        }catch(e){
            console.log('error')
            notFound();
        }
}

 

axios.interceptor - 왜 동기적으로한건 무시가 안되고 비동긴 무시가되나요?

const axios = _axios.create();
axios.interceptors.response.use(
    function(res){
        return res
    },
    function(err){
        if(err){
            console.log('interceptors')
            notFound(); // not working
        }
    }
)
...
async () => {
    try{
        await axios.get('<http://127.0.0.1:3001/error>'); // 404 error
        console.log('success');
    }catch(e){
        console.log('error')
        notFound()
    }
}

 

sync fetch

const axios = _axios.create();
axios.interceptors.response.use(
    function(res){
        return res
    },
    function(err){
        if(err){
            console.log('interceptors')
            notFound();
        }
    }
)
...
try{
    axios.get('<http://127.0.0.1:3001/error>'); // 404 error
    console.log('success');
}catch(e){
    console.log('error');
    notFound();
}

 

component rendering working case

const [data, setData] = useState('init');
  if(data === 'error') {
      notFound();
}

useEffect(()=> {
    (async () => {
        try{
            await axios.get('<http://127.0.0.1:3001/error>'); // 404 error
            console.log('success');
        }catch(e){
            console.log('error')
            setData('error')
        }
    })()
}, [])

SSR

docs example case

try{
  await axios.get('<http://127.0.0.1:3001/error>'); // 404 error
  console.log('success');
}catch(e){
  console.log('error')
  notFound()
}

 

ignored axios interceptors also SSR -> view Rendering Page not 404

const axios = _axios.create();
axios.interceptors.response.use(
    function(res){
        return res
    },
    function(err){
        if(err){
            console.log('interceptors')
            notFound(); // not working
        }
    }
)
...
const AxiosInterceptors = () => {
    try{
        axios.get('<http://127.0.0.1:3001/error>'); // 404 error
        console.log('success');
    }catch(e){
        console.log('error');
    }

    return (
        <div>
            Rendering Page
        </div>
    )
}

 

syncAxiosInterceptors - ‘use client’와 동일하게 에러가 납니다.

axios.interceptors.response.use(
    function(res){
        return res
    },
    function(err){
        if(err){
            console.log('interceptors')
            notFound();
        }
    }
)
...
try{
    axios.get('<http://127.0.0.1:3001/error>'); // 404 error
    console.log('success');
}catch(e){
    console.log('error');
}

💡 React Query의 onSuccess, onError에서 사용을 해보려다가 정상적으로 동작하지 않는 것을 보고, fetch로까지 테스트해보면서 발견한 케이스들입니다.


그래서 코드를 살펴보고 직접 원인을 찾기로 했습니다.

Next.js에 가서 notFound() 함수에 대해서 살펴봤습니다.

오호.. 그러니까 notFound는 custom error를 throw해주는 간단한 역할이였습니다.

그걸 NotFoundErrorBoundary컴포넌트에서 잡아다가 화면을 404컴포넌트로 변환해주고 있던것입니다.

생각보다 단순한 로직에 놀라면서, ErrorBoundary의 특징에 대해서 찾아보기 시작했습니다.

… 그렇습니다. 비동기랑 이벤트 핸들러에서 발생하는 에러는 잡아주지 않습니다. 딱 정상적으로 작동하지 않던 notFound들의 경우의 수입니다.

이해를 못했던 이유가 몇가지가 있는데

  1. Errorboudary에서 잡는 것을 모르고 있었습니다. notFound()가 단순히 컴포넌트 이동만 시키는 것으로 생각하고 있었습니다.
  2. useEffect가 비동기처럼 동작하다는 설명을 비동기로 구현된 것이라고 생각했습니다. 그래서 useEffect 중 일부는 동작하고, 내부에서 비동기를 포함했을 땐 동작하지 않는 이유가 이해가지 않았습니다.
  3. 이벤트 핸들러에선 동작하지 않는질 모르고 있었습니다.

  • 비동기에선 동작 X
    • useEffect는 동기적으로 동작
  • 이벤트 핸들러에선 동작

이 두가지 개념을 정확하게 들고갔다면 제대로 동작을 이해할 수 있었을 것입니다.


Answer

  1. ‘use client’의 useEffect의 동작이 의존성에 따라서 다르게 동작하는 이유가 뭔가요?
    1. 의존성이 아닌 비동기의 문제였습니다.
  2. axios.interceptors를 동기적으로 실행했을 때는 notFound()가 에러를 뱉고, 비동기로 했을때는 동작이 무시되는 이유가 뭔가요?
  3. 클릭이벤트에선 왜 에러를 뱉나요?
    1. 2, 3번 한꺼번에 답변하면, Errorboundary의 경우 비동기와 이벤트 핸들러에서 발생시킨 에러는 잡지 못하기 때문입니다.

 

참조

https://ko.legacy.reactjs.org/docs/error-boundaries.html

https://velog.io/@superlipbalm/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior

Comments