onClickExamples

Next Parallel Route

Simple way to making parallel route using Typescript and Next 14.

This example uses Fake Store API for dummy products.

Installation

Create a new Next.js project:

Terminal

npx create-next-app@latest my-app --typescript --tailwind --eslint

File Structure

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

Files

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

<- Home
End