updated 01/13/24
Next.js 로 포트폴리오 사이트를 만들면서
Route 로 관리되는 모달 (Route as Modal) 을 구현하고자 하였다
리액트로 같은 기능을 구현할 때 useHistory 로 구현하였던 기억이 있어 비슷하게 헤매일 줄(?) 알았는데
생각보다 훨씬 간단하였다 (머 - 쓱 😅)
Next js 는 그만큼 라우터에 진심이라는 것을 다시한번 느끼며 그때의 감동을 정리해보았다
Next.js - Route 로 관리하는 모달 만들기 (URL 히스토리)
일단 처음 살펴봐야할 개념이 있다. Intercepting Routes 라는 개념이다.
이 친구에 대해 간단히 정리를 하면 원래의 주소의 화면과 다른 라우트를 가진 컴포넌트를 동시에 보여줄 수 있는 기능이다
This allows you to load a route from another part of your application within the current layout.
For example, when clicking on a photo in a feed, you can display the photo in a modal, overlaying the feed. In this case, Next.js intercepts the /photo/123 route, masks the URL, and overlays it over /feed.
→ 딱 나에게 맞는 예시가 아닐까 싶다
방법은 너 - 무 간단하다. 디렉터리 구조를 아래와 이란성 쌍둥이처럼 꾸려주는 것이다
자료 이미지를 보면 빠른 이해가 가능할 것이다
- (괄호로 감싸진 상대 경로) 로 표현한 디렉터리 구조
- 일반적으로 표현한 디렉터리 구조
App Directory
디렉터리 자체를 모달일 때와 페이지일 때의 경로로 구분해주었다
- @modal 디렉터리 내에 모달로 된 page 를 셋업해주었고
- portfolio 디렉터리 내에 페이지로 된 page 를 셋업해주었다
(page.tsx 파일명은 컨벤션이다)
components/common/modal
모달 자체의 기능을 정의한 컴포넌트이다
"use client";
import React, {
HTMLAttributes,
ReactNode,
useCallback,
useEffect,
useRef,
} from "react";
import { useRouter } from "next/navigation";
export interface ModalProps {
children: React.ReactNode;
}
const Modal = ({
children,
}: ModalProps) => {
const router = useRouter();
const clickedRef = useRef<EventTarget>();
// 닫기 기능
const onClose = useCallback(() => {
router.back(); // 라우터 뒤로가기 기능을 이용
}, [router]);
function handleClickClose(e: React.MouseEvent<HTMLElement>) {
if (clickedRef.current) {
clickedRef.current = undefined;
return;
}
e.stopPropagation();
onClose();
}
return (
// 모달 외부
<div
className={`modal_outer`}
onMouseUp={handleClickClose}
>
// 모달 내부
<div
className={`modal_inner`}
onMouseDown={(e) => clickedRef.current = e.target}
onMouseUp={(e) => clickedRef.current = e.target}
>
{children}
<button onClick={handleClickClose}> 닫기 </Button>
</div>
</div>
);
};
export default Modal;
@modal/(.)portfoio/[portfolioID]/page
일단 모달일 때 뜨는 컴포넌트는 여기서 정의한다!
// 모달일 때
const PortfolioModal = async ({ params }: { params: { id: string };}) => {
const { id } = params;
const portfolio = await getPostData(id); // get 메소드
return (
<Modal> // 모달 공통 컴포넌트를 만들고
<PortfolioMain portfolio={portfolio} /> // 자식 컴포넌트1
<PortfolioInfo portfolio={portfolio} /> // 자식 컴포넌트2
</Modal>
);
};
export default PortfolioModal;
portfolio/[portfolioID]/page
이친구는 해당 페이지로 정직하게 주소창(?)를 치고 들어갔을 때 우리를 반기는 페이지 컴포넌트이다 (비록 모달에게 밀렸지만)
// 페이지 일 때
const PortfolioDetail = async ({ params }: { params: { id: string }}) => {
const { id } = params;
const portfolio = await getPostData(id);
return (
<div>
<h1>{portfolio.title}</h1>
<p>{portfolio.date}</p>
.
.
.
</div>
);
};
export default PortfolioDetail;
app/layout.tsx
레아이웃 파일은 최상단 디렉터리인 page(/)와 하위 page(/portfolio)에 공통으로 적용되는 레이아웃이다.
import { Noto_Sans_KR } from "next/font/google";
const notoSansKr = Noto_Sans_KR({ // implement google font!
subsets: ["latin"],
weight: ["400", "500", "700"],
});
export const metadata: Metadata = {
title: "SBJA's Portfolio",
description: "Welcome to SBJA's Portfolio",
};
export default function RootLayout({
children,
modal, // @으로 시작하는 디렉터리명
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<html lang="en" className={notoSansKr.className}>
<body>
<Header />
{children}
<Footer />
{modal} // 모달 컴포넌트는 여기에 위치
</body>
</html>
);
}
app/page.tsx
여기는 app 라우터의 최상단! 메인 페이지의 컴포넌트들이 위치하는 홈이다
export default function Home() {
const portfolios = getPortfolioList();
return (
<main>
{portfolios.map(item =>
<Link href={`/portfolio/${item.id}`}> // url 이동을 하게 되면 로직을 타고 모달이 뜬다!
포트폴리오 {item.id} 페이지로 이동
</Link>
}
</main>
);
}
결과화면 미리보기
아주 만족 스럽다! 성 - 공 :)
'Next' 카테고리의 다른 글
Strapi(1) 스트라피 설치하기 (0) | 2024.02.23 |
---|---|
Next.js - 서버 컴포넌트에서 useContext() 사용하기 (0) | 2023.12.31 |
Next.js - global 구글 폰트 적용하기(next/font) (0) | 2023.08.17 |
Next.js - SVG 사용해서 컬러, 사이즈 변경하기 (Feat.svrg) (1) | 2023.05.14 |
Next.js - Styled-components 적용전 렌더링 되는 이슈 (0) | 2023.04.16 |
댓글