노현진's Blog

[트러블슈팅] Next.js + react-photo-view

Next.js MDX 블로그 프로젝트에서 react-photo-view 라이브러리를 사용하면서 발생한 빌드 오류를 해결한 과정에 대해 정리한 페이지입니다.

Posted
By HyunJinNo

Tags

Troubleshooting, Typescript, Next.js

1. ✅ 개요

Next.js MDX 블로그 프로젝트에서 react-photo-view 라이브러리를 사용하면서 발생한 빌드 오류를 해결한 과정에 대해 정리한 페이지입니다.

2. ❓ 문제

2.1. ⚠️ 오류

Tips

발생한 버그를 간략히 설명해 주세요.

다음괴 같이 Next.js 애플리케이션 빌드 시 Prerender Error가 발생하였습니다.

bash
1user@MacBookProui-MacBookPro mdx-blog % npm run build
2> mdx-blog@1.0.0 build
3> next build
4• Next.js 16.1.4 (Turbopack)
5Creating an optimized production build
6• Compiled successfully in 1492.4ms
7• Finished TypeScript in 945.7ms
8• Collecting page data using 11 workers in 261.5ms
9Error occurred prerendering page "/posts/2025-04-07-fsd-example-nextjs". Read more: https://nextjs.org/docs/messages/prerender-error
10Error: Element type is invalid: expected a string (for built-in components) or a class/function
11(for composite components) but got: undefined.
12at ignore-listed frames {
13digest: 3649311722'
14Export encountered an error on /posts/[post]/page: /posts/2025-04-07-fsd-example-nextjs, exiting the build.
15× Next. js build worker exited with code: 1 and signal: null ry user@MacBookProui-MacBookPro mdx-blog%|

2.2. 🖥️ 발생 환경

Tips

운영체제, 브라우저, 의존성 목록 등을 작성해 주세요.

  • OS: MacOS
  • Next.js 16.1.4
  • react-photo-view v1.2.7

2.3. 🕘 발생 일시

Tips

버그가 발생한 날짜와 시간을 입력해 주세요. (Ex. 2024년 10월 1일, 오후 3시 30분)

  • 2026년 2월 28일, 오후 4시 30분

3. 📖 해결 과정

먼저 에러 메시지에 따라 /posts/2025-04-07-fsd-example-nextjs 파일에서 Prerender Error가 발생하는 것을 알 수 있었고, Element type is invalid ... but got: undefined가 에러의 핵심임을 파악할 수 있었습니다.

따라서 문단 단위로 지웠다가 다시 복구하면서 어느 부분에서 오류가 발생하는지 찾으려고 하였고, 다음 사진과 같이 3. FSD 아키텍처 적용하기 문단에서 오류가 발생함을 확인할 수 있었습니다.

그러나 오류 발생 위치를 파악하였지만, 원인 및 해결 방법에 대해선 찾기 어려웠습니다. 특히 위의 사진과 같이 이미지와 텍스트 위치를 서로 바꾸었다고 빌드가 성공했다는 점이 이해하기 어려웠습니다.

이와 같은 상황에서 오류의 원인이 이미지와 관련되어 있지 않을까라고 생각하였습니다. 특히 이전까지 발생하지 않았던 오류가, react-photo-view 라이브러리를 사용한 이후부터 발생했다는 점에서 그런 생각이 들었습니다.

typescript
1/* @/mdx-components.tsx */
2
3/* ... */
4
5import type { MDXComponents } from "mdx/types";
6import { PhotoView } from "./shared/ui/photo";
7
8const components = {
9  /* ... */
10
11  img: (props) => (
12    <span className="my-2 flex items-center justify-center">
13      <PhotoView src={props.src}>
14        {/* eslint-disable-next-line @next/next/no-img-element */}
15        <img
16          {...props}
17          className="cursor-zoom-in rounded-lg border border-gray-200 shadow-[0_4px_8px_0_rgba(0,0,0,0.2),0_6px_20px_0_rgba(0,0,0,0.19)]"
18          alt={props.alt}
19        />
20      </PhotoView>
21    </span>
22  ),
23
24  /* ... */
25} satisfies MDXComponents;
26
27export function useMDXComponents(): MDXComponents {
28  return components;
29}
typescript
1/* @/shared/ui/photo/PhotoView.tsx */
2
3"use client";
4
5import { JSXElementConstructor, ReactElement } from "react";
6import { PhotoView as BasePhotoView } from "react-photo-view";
7
8interface PhotoViewProps {
9  src: string;
10  children: ReactElement<unknown, string | JSXElementConstructor<unknown>>;
11}
12
13export const PhotoView = ({ src, children }: PhotoViewProps) => {
14  return <BasePhotoView src={src}>{children}</BasePhotoView>;
15};

위에서 사용하고 있는 PhotoView 컴포넌트는 서버 컴포넌트를 prop으로 전달받고 있습니다. 위와 같은 코드에서 return <BasePhotoView src={src}>{children}</BasePhotoView>;return children;으로 대신하였더니 오류가 발생하지 않았습니다. 즉, 오류의 원인은 react-photo-view 라이브러리를 사용하는 부분에 있다는 점을 파악할 수 있었습니다.

react-photo-view 라이브러리를 사용하면서 왜 오류가 발생했는지 파악하기 위해, 먼저 react-photo-view 라이브러리 저장소를 찾아 코드를 확인해보았습니다.

코드를 확인해보니 react-photo-viewPhotoView 컴포넌트는 useEffect, useRef 등을 사용하는 등 완전한 CSR 전용 컴포넌트임을 확인할 수 있었습니다.

제가 사용하고 있는 MDX 블로그는 SSG 환경이었고, 정적 생성(SSG) 단계에서 import 자체는 서버에서 실행되기 때문에 발생한 문제였습니다. 따라서 react-photo-view 라이브러리는 SSR 환경에서는 안전하지 않기 때문에 브라우저에서만 로딩되도록 방지할 필요가 있었습니다.

Prerender Error with Next.js | Next.js

실제 공식 문서에서도 Prerender Error 해결 방법 중 하나로 5. Disable server-side rendering for components using browser APIs를 설명하고 있었습니다.

따라서 다음과 같이 next/dynamic로 클라이언트 사이드(= 브라우저)에서 react-photo-view를 불러오도록 설정하였고, 이후 빌드 시 오류가 발생하지 않게 되었습니다.

typescript
1/* @/shared/ui/photo/PhotoView.tsx */
2
3"use client";
4
5import dynamic from "next/dynamic";
6import { JSXElementConstructor, ReactElement } from "react";
7
8const BasePhotoView = dynamic(
9  () => import("react-photo-view").then((mod) => mod.PhotoView),
10  { ssr: false },
11);
12
13interface PhotoViewProps {
14  src: string;
15  children: ReactElement<unknown, string | JSXElementConstructor<unknown>>;
16}
17
18export const PhotoView = ({ src, children }: PhotoViewProps) => {
19  return <BasePhotoView src={src}>{children}</BasePhotoView>;
20};

4. 📚 참고 자료

This post is licensed under CC BY 4.0 by the author.
공유하기:

© HyunJinNo. Some rights reserved.