1. Next.js? 그냥 react 개발하듯이 하면 되지 않나?
방학 동안, Next.js 14를 익히고 싶어서 Next.js로 자기소개 사이트를 만들고 있다. 블로그 글을 읽으며 개발할 때 React와 어떤 차이점이 있는지 읽고 바로 작업에 들어갔다. 작업을 하며 라우팅 방식이나 서버 컴포넌트 같은 차이점 외에는 React와 별 다른 점이 없다고 생각하기도 했었다.(header 밖에 안 만들었을 때 그런 생각을 한 게 참…ㅋㅋㅋ). 하지만, 그 생각은 길게 유지되지 않았다. 왜냐하면 css 애니메이션이 동작하지 않았기 때문이다.
2. css 애니메이션이 작동하지 않아요
내가 좋아하는 음악의 앨범 커버, 노래 제목, 가수 이름이 적혀있는 MusicCard 컴포넌트가 있고, 그 MusicCard 컴포넌트들이 일렬로 오른쪽에서 왼쪽으로 무한히 재생되는 컴포넌트인 MusicSlide를 제작하려고 했다. 따라서 MusicSlide.css를 생성해서 애니메이션(transition, transform..) 코드를 입력했다.(tailwind css를 쓰고 싶었지만 애니메이션 관련 코드들은 따로 파일로 만들었다.)
문제점은, 애니메이션이 재생이 되지 않는다는 것이었다! 그런데 이상하게도 vscode에서 코드를 수정하고 저장하면 그 때는 애니메이션이 재생이 되었다. 도대체 왜 이런 일이 발생했나 이해가 되지 않았다. 혹시 로컬 환경이라서 이런 일이 발생했나 싶어서 vercel로 배포를 해서 살펴봤는데도 애니메이션이 재생되지 않았다. 코드를 이리저리 수정해봐도 도저히해당 버그가 수정되지 않았다.
그러다가 문득 내가 Next.js를 써서 이런 일이 발생했나하는 생각이 들었다. 그래서 해당 컴포넌트에서 next/dynamic을 import해와서 ssr 옵션을 false로 만들어줬다(해당 컴포넌트를 서버 사이드 렌더링이 아닌, 클라이언트 사이드 렌더링(CSR)로 구동시킨다는 뜻). 그랬더니 애니메이션이 정상적으로 작동이 되었다!
'use client';
import React, { useRef, useEffect, useState } from 'react';
// 애니메이션 작동을 위해서 dynamic import를 통해 CSR 강제
import dynamic from 'next/dynamic';
import MusicCard from './MusicCard';
import slides from '@/constants/Slides';
import uuid from 'react-uuid';
import './MusicSlide.css';
const MusicSlide = ({ reverse = false }) => {
const carouselRef = useRef<HTMLDivElement>(null); // 캐러셀의 참조를 저장할 useRef를 선언
const loadedImageCount = useRef(0); // 로드된 이미지 수를 저장할 useRef를 선언
const [isLoaded, setIsLoaded] = useState(false);
(...)
}
export default dynamic(() => Promise.resolve(MusicSlide), { ssr: false });
3. 서버 사이드 렌더링에서는 왜 css 애니메이션이 동작하지 않을까 물어봤다
서버 사이드 렌더링 환경에서 왜 애니메이션이 작동되지 않았을까. 바로 Slack에 질문을 남겼다.
그리고 머지않아 답변이 왔다.
SSR 환경에서는 ref를 통한 DOM 조작이 동작하지 않아서 그런 일이 발생한 것이었다.
정리하자면, 위의 사진과 같다. SSR 환경에서는 ref를 통한 DOM 요소의 접근이 잘 동작하지 않아서 css 애니메이션이 재생되지 않았던 것이었다. 그리고 이에 대한 해결책으로는 forwardRef를 통해서 DOM 요소에 접근하면 되는 것이었다.
4. 그러면 이제 고쳐볼까
따라서 MusicSlide.tsx에 개별 이미지 요소를 가리키는 ref 배열인 imageRefs를 새로 만들어서 MusicCard.tsx에 전달해줬다.
MusicSlide.tsx 에서
// 각각의 MusicCard 컴포넌트 내의 개별 img 요소를 가리키는 ref 배열
const imageRefs = useRef(slides.map(() => createRef<HTMLImageElement>()));
MusicCard.tsx에서
import React, { forwardRef } from 'react';
interface Props {
albumCover: string;
songTitle: string;
artist: string;
onLoad: () => void;
}
// forwardRef를 사용함으로써 DOM 조작 가능
const MusicCard = forwardRef<HTMLImageElement, Props>(
({ albumCover, songTitle, artist, onLoad }, ref) => {
return (
<div className="p-8">
<img
ref={ref}
src={albumCover}
alt="Album Cover"
className="w-48 h-24 rounded-t-lg object-contain"
onLoad={onLoad}
/>
<div className="text-left">
<h3 className="text-xs font-semibold whitespace-nowrap">
{songTitle}
</h3>
<p className="text-xs text-gray-500 whitespace-nowrap">{artist}</p>
</div>
</div>
);
}
);
export default MusicCard;
5. 하지만 고치지 못했다
하지만 애니메이션은 실행이 안되었다.... 애초에 Next.js를 사용하면서 css 애니메이션을 활용하려는 것이 잘못된 접근이었나하는 생각이 들어서 새로운 애니메이션 라이브러리를 도입해볼까 한다.
이런 시행착오 속에서, 비록 버그를 고치지는 못했지만 ref와 forwardRef에 대해서, Server Side Rendering의 구조에 대해서 더 확실히 배울 수 있었다. 이 또한 값진 경험이겠지...ㅋㅋㅋ