nextjs-v15

App Router - 병렬 라우트와 인터셉팅 라우트

App router

병렬 라우트

하나의 화면에 여러 개를 렌더링 시키는 것


// src/app/parallel/@sidebar/page.tsx

// src/app/parallel/layout.tsx
import { ReactNode } from "react";

export default function Layout({
  children,
  sidebar,
}: {
  children: ReactNode;
  sidebar: ReactNode;
}) {
  return (
    <div>
      {children}
      {sidebar}
    </div>
  );
}

소셜 미디어 서비스나 관리자 대시보드 등 복잡한 구조를 갖는 UI에 유용하게 활용된다.

폴더 이름 앞에 골뱅이가 붙은 것을 슬롯이라고 부른다.

슬롯 : 병렬로 렌더링 될 페이지 컴포넌트를 보관하는 폴더

부모 폴더의 레이아웃에 props로 전달된다.

슬롯의 경로는 무시하기 때문에 슬롯 내부에 하위 경로 폴더가 없는 경우 그냥 무시하고 이전 페이지를 보여준다. 이때 주의 할 점은 새로고침 할 경우에는 이전 페이지가 존재하지 않기 때문에 404 에러를 발생시킨다는 것이다. 따라서 슬롯별로 렌더링 할 페이지가 없을 경우를 대비해 default 페이지를 만들어야 한다. (각 슬롯 내부에 default.tsx 생성)

image

여기서 만약 http://localhost:3000/parallel/a 로 접속했다면, 보라색 페이지만 업데이트 되고, 나머지 파란색과 초록색 페이지는 그대로 유지된다.

  • 만약 에러 발생하면 .next 폴더 제거 후 다시 실행
  • 슬롯 개수는 제한이 없다

❌인터셉팅 라우트

특정 조건에 따라 원래 페이지가 아닌 다른 페이지 렌더링 하는 것

설명

여기서 특정 조건이란? 개발자가 임의로 설정할 수 없다. 기본적으로 세팅되어있는 조건이고, 초기 접속이 아닌 경우에 동작하도록 설정된다.

ex) 인스타그램에서 게시글을 눌렀을 때 모달 형태로 보여주다가, 새로고침 할 경우 아예 상세 페이지로 넘어가버리는 것

구현

(.)book/[id] 이렇게 작성하면 현재 동일 경로에 존재하는 book/[id] 가로챈다.

만약 한 경로 상위에 있다면 (..), 두 경로 상위에 있다면 (..)(..) 를 적어준다.

(…) 은 app 폴더 바로 아래에 있는 경로를 말한다.

// 인터셉팅 페이지 (기존 페이지 그대로 렌더링 되도록 porps 설정)
import BookPage from "@/app/book/[id]/page";

export default function Page(props:any) {
	return(
		<div>
			<Modal>
				<BookPage {...props} />
			</Modal>
		</div>
	)
}
// src/components/modal.tsx 모달생성
"use client";

import {ReactNode} from "react";
import style from "./modal.module.css";
import {createProtal} from "react-dom";
import {useRouter} from "next/navigation"

export default function Modal({children} : {children: ReactNode}) {
	
	const dialogRef = useRef<HTMLDialogElement>(null);
	const router = useRouter();
	
	useEffect(()=> {
		if(!dialogRef.current?.open) { // 모달이 닫혀있는 경우
			dialogRef.current?.showModal(); // 모달 열어두기
			dialogRef.current?.scrollTo({top:0}); // 스크롤 위치 맨 위로
		}
	}, []);
	
	return createPortal(
		<dialog 
			onClose={() => router.back()} // ESC 눌렀을때
			onClick={(e) => { // 바깥쪽 눌렀을때
				if((e.target as any).nodeName === "DIALOG") {
					router.back();
				}
			}}
			className={style.modal}
			ref={dialogRef}
		>
			{children}
		</dialog>,
		document.getElementById("modal-root") as HTMLElement
	)
}

// src/app/layout.tsx
	body 바로 아래에
	<div id="modal-root"></div>
</body>
// 모달 스타일
.modal {
	width: 80%;
	max-width: 500px;
	
	margin-top: 20px;
	border-radius: 5px;
	border:none;
}

.modal::backdrop { // 모달 뒷부분 흐릿하게
	background: rgba(0, 0, 0, 0.7);
}

🤚 병렬 라우트와 인터셉팅 라우트 같이 쓰는 거 좋음