Simple way to making parallel route using Typescript and Next 14.
This example uses Fake Store API for dummy products.
Create a new Next.js project:
Terminal
npx create-next-app@latest my-app --typescript --tailwind --eslint
This is a basic file structure for this example.
Tree
app/
├── page.tsx
├── globals.css
├── layout.tsx
└── products/
├── page.tsx
├── layout.tsx
└── default.tsx/
└── @modal/
├── page.tsx
└── [id]/
└── page.tsx
components/
└── modal.tsx
Create a page.tsx file inside of products folder. This file contains fetch and list data.
products/page.tsx
import Link from "next/link";
import Image from "next/image";
async function fetchData() {
const response = await fetch("https://fakestoreapi.com/products");
const data = await response.json();
return data;
}
export default async function UsersPage() {
const data = await fetchData();
return (
<div>
{data.map((item: any) => (
<Link scroll={false} href={`/products/${item.id}`} key={item.id}>
<Image
width={320}
height={320}
src={item.image}
alt={item.title}
/>
{item.title}
</Link>
))}
</div>
);
}
Create a layout.tsx file inside of products folder and set slots.
products/layout.tsx
export default function RootLayout({
children,
modal,
}: Readonly<{
children: React.ReactNode;
modal: React.ReactNode;
}>) {
return (
<div>
{children}
{modal}
</div>
);
}
You can define a default.js file to render as a fallback for unmatched slots during the initial load or full-page reload.
products/default.tsx
import Page from "./page";
export default Page;
Create @modal folder and page.tsx in it. You can rename @modal folder whatever you prefer.
products/@modal/page.tsx
export default function Page() {
return null;
}
Inside of the @modal, create [id]/page.tsx
products/@modal/[id]/page.tsx
import { Suspense } from "react";
import Image from "next/image";
import { Modal } from "@/components/modal";
async function fetchId(id: string) {
const response = await fetch(`https://fakestoreapi.com/products/${id}`);
const data = await response.json();
return data;
}
export default async function ProductDetail({
params,
}: {
params: { id: string };
}) {
const data = await fetchId(params.id);
return (
<Modal closeHref="/products">
<Suspense fallback={<div>Loading...</div>}>
// insert HTML inside of fallback or import component
<div>
<Image width={320} height={320} src={data.image} alt={data.title} />
<div>
<h1>{data.title}</h1>
<p>{data.description}</p>
<p>Price: ${data.price}</p>
</div>
</div>
</Suspense>
</Modal>
);
}
and last step is creating basic modal component. You can write CSS whatever you needed.
components/modal.tsx
import { ReactNode } from "react";
import Link from "next/link";
interface ModalProps {
children: ReactNode;
closeHref: string;
}
export function Modal({ children, closeHref }: ModalProps) {
return (
<div className="modal-backdrop">
<div className="modal-content">
<Link href={closeHref}>
X
</Link>
{children}
</div>
</div>
);
}
Completed:
Next Parallel Route