Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Breadcrumbs example #914

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
"name": "examples",
"license": "MIT",
"private": true,
"engines": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this explicit?

"pnpm": "8.15.1"
},
"scripts": {
"lint-staged": "lint-staged",
"new-example": "plop example",
Expand Down
3,633 changes: 2,024 additions & 1,609 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions solutions/parallel-routes-navbar/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"root": true,
"extends": "next/core-web-vitals"
}
42 changes: 42 additions & 0 deletions solutions/parallel-routes-navbar/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# Dependencies
/node_modules
/.pnp
.pnp.js

# Testing
/coverage

# Next.js
/.next/
/out/
next-env.d.ts

# Production
build
dist

# Misc
.DS_Store
*.pem

# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Local ENV files
.env.local
.env.development.local
.env.test.local
.env.production.local

# Vercel
.vercel

# Turborepo
.turbo

# typescript
*.tsbuildinfo
33 changes: 33 additions & 0 deletions solutions/parallel-routes-navbar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# parallel-routes-navbar example

This example shows how -------------------------

## Demo

https://solutions-parallel-routes-navbar.vercel.app

## How to Use

You can choose from one of the following two methods to use this repository:

### One-Click Deploy

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=vercel-examples):

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/solutions/parallel-routes-navbar&project-name=parallel-routes-navbar&repository-name=parallel-routes-navbar)

### Clone and Deploy

Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:

```bash
pnpm create next-app --example https://github.com/vercel/examples/tree/main/solutions/parallel-routes-navbar
```

Next, run Next.js in development mode:

```bash
pnpm dev
```

Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=edge-middleware-eap) ([Documentation](https://nextjs.org/docs/deployment)).
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default function BreadcrumbLoading() {
return (
<nav className="flex gap-4 text-sm opacity-80">
<div className="flex gap-1 text-sm">
<span className="capitalize">{`>`}</span>
<span className="w-[36px] bg-gray-200 animate-pulse rounded"></span>
</div>
<span> / </span>
<span className="w-[55px] bg-gray-200 animate-pulse rounded"></span>
</nav>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const DATA = {
'key-lime-pie': 'Key Lime Pie',
'pumpkin-pie': 'Pumpkin Pie',
'apple-pie': 'Apple Pie',
}

export default async function BreadcrumbPage({
params: { category, slug },
}: {
params: { category: string; slug: string }
}) {
const displayName = await new Promise<string>((resolve) =>
setTimeout(
() => resolve(DATA[slug as keyof typeof DATA] || 'Secret recipe'),
1000
)
)
Comment on lines +12 to +17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nit: I would move this as a async function above to simplify the function body.


return (
<nav className="flex gap-4 text-sm opacity-80">
<span className="capitalize">{` > ${category}`}</span>
<span> / </span>
<span>{displayName}</span>
</nav>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const DATA = {
'key-lime-pie': `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi porttitor
accumsan vestibulum. Mauris dictum hendrerit ex. Ut lacus libero, feugiat
sed turpis non, vehicula eleifend ex. Nulla in augue interdum, suscipit
diam eu, aliquam magna. Etiam convallis lacus mattis nulla aliquam, quis
tempor ante accumsan. Aenean non libero tempus mi eleifend pulvinar id ac
nulla. Nullam at nisi sed augue mattis varius ac quis purus. Vivamus
mollis arcu placerat venenatis commodo. Donec et dui iaculis, tristique
diam vel, commodo erat. In vel orci quis orci feugiat laoreet.`,
'pumpkin-pie': `Donec et dui iaculis, tristique diam vel, commodo erat.
In vel orci quis orci feugiat laoreet. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Morbi porttitor accumsan vestibulum. Mauris dictum hendrerit ex.
Ut lacus libero, feugiat sed turpis non, vehicula eleifend ex. Nulla in augue interdum, suscipit
diam eu, aliquam magna. Etiam convallis lacus mattis nulla aliquam, quis
tempor ante accumsan. Aenean non libero tempus mi eleifend pulvinar id ac
nulla. Nullam at nisi sed augue mattis varius ac quis purus. Vivamus
mollis arcu placerat venenatis commodo.`,
'apple-pie': `Nulla in augue interdum, suscipit
diam eu, aliquam magna. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi porttitor
accumsan vestibulum. Mauris dictum hendrerit ex. Ut lacus libero, feugiat
sed turpis non, vehicula eleifend ex. Etiam convallis lacus mattis nulla aliquam, quis
tempor ante accumsan. Aenean non libero tempus mi eleifend pulvinar id ac
nulla. Nullam at nisi sed augue mattis varius ac quis purus. Vivamus
mollis arcu placerat venenatis commodo. Donec et dui iaculis, tristique
diam vel, commodo erat. In vel orci quis orci feugiat laoreet.`,
}

export default function SlugPage({
params: { slug },
}: {
params: { slug: string }
}) {
return (
<main>
{DATA[slug as keyof typeof DATA] ||
`Integer et lacus orci.
Integer non libero vestibulum, convallis erat ac, euismod mauris. Etiam
auctor nulla sed neque egestas condimentum. Duis sed metus malesuada,
cursus massa vitae, feugiat est.`}
</main>
)
}
34 changes: 34 additions & 0 deletions solutions/parallel-routes-navbar/app/(demo)/demo/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { ReactNode } from 'react'

import '@vercel/examples-ui/globals.css'
import Link from 'next/link'

export default function RootLayout({
children,
breadcrumb,
}: {
children: ReactNode
breadcrumb: ReactNode
}) {
return (
<html lang="en">
<body className="container max-w-screen-sm m-auto grid gap-4 p-4">
<header>
<ul className="flex gap-4 font-medium underline">
<li>
<Link href="/demo/pastries/key-lime-pie">Key Lime Pie</Link>
</li>
<li>
<Link href="/demo/cakes/pumpkin-pie">Pumpkin Pie</Link>
</li>
<li>
<Link href="/demo/cakes/apple-pie">Apple Pie</Link>
</li>
</ul>
{breadcrumb}
</header>
<main>{children}</main>
</body>
</html>
)
}
19 changes: 19 additions & 0 deletions solutions/parallel-routes-navbar/app/(example)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { ReactNode } from 'react'
import { Layout, getMetadata } from '@vercel/examples-ui'

import '@vercel/examples-ui/globals.css'

export const metadata = getMetadata({
title: 'parallel-routes-navbar',
description: 'parallel-routes-navbar',
})

export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<Layout path="solutions/parallel-routes-navbar">{children}</Layout>
</body>
</html>
)
}
127 changes: 127 additions & 0 deletions solutions/parallel-routes-navbar/app/(example)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Page, Text, Code, Link, Snippet } from '@vercel/examples-ui'

export default function Home() {
return (
<Page className="flex flex-col gap-12">
<section className="flex flex-col gap-6">
<Text variant="h1">
Using parallel routes to display route related information in layouts
</Text>
<Text>
Breadcrumbs can be found in e-commerce, blogs, documentation, and many
other types of websites. They are a great way to help users understand
where they are in the website hierarchy and navigate back to previous
pages. If one should say where they should be placed at code level,
it&apos;s probably in the layout shared between all the pages that has
it. But sometimes it&apos;s not that simple. To show information about
the current route, you might need information from the url, like the
params, which is not available in layouts.
</Text>
<Text>
A solution for this might be create the breadcrumbs component as a
client component, use a hook to get the params and then display it.
But, what happen if the parameter in the url is a slug, and we have to
first get the display value for it from a service before showing it on
the breadcrumb? Because it&apos;s a client component we have to waiy
for the server to send the response, for the javascript to download
and execute, for the call to the slug service to complete and then
finally display that breadcrumb to the user.
</Text>
<Text>
Wow, that sounds like a lot of work right? Well, or we can use a
parallel route 👇
</Text>
</section>

<section className="flex flex-col gap-3">
<Text variant="h2">Pages as components</Text>
<Text>
You can image parallel routes as slots in your page. Your layout
receives a prop <Code>children</Code> that has the content of the
route to display. With parallel routes you can have more of this slots
where you decide what to display in each one. And, like pages, they
receive the same props, including the params.
</Text>
</section>

<section className="flex flex-col gap-3">
<Text variant="h2">Let&apos;s do it</Text>
<Text>
You were given a task to update a bakery blog project. You have to
show a breadcrumb with the category and its icon and the post title on
it.
</Text>
<Text>
You can&apos;t touch the <Code>page</Code> file because it&apos;s
being handled by a CMS so you can only change the layout or add new
files to add the breadcrumb. Let&apos;s start by creating a parallel
route for our breadcrumb:
</Text>
<Snippet>
{`|/app
|__/[category]
|____/[slug]
|______/page.js
|__/layout.js
|__/@breadcrumb
|____/[category]
|______/[slug]
|________/page.js`}
</Snippet>
<Text>
Now we have a parallel route called <Code>breadcrumb</Code> that will
match when someone requests the <Code>/category/slug</Code> page.
Let&apos;s add the parallel route to our layout.
</Text>
<Snippet>
{`
export default function RootLayout({ children, breadcrumb }) {
return (
<html lang="en">
<body>
<header>
<Navbar />
{breadcrumb}
</header>
<main>
{children}
</main>
</body>
</html>
)
}
`}
</Snippet>
<Text>
In our <Code>/@breadcrumb/[category]/[slug]/page.js</Code> let&apos;s
get category and slug, fetch the display name and display them on
screen.
</Text>
<Snippet>
{`export default async function BreadcrumbPage({ params: { category, slug } }) {
const displayName = await fetch(\`.../\${category}/\${slug}\`);

return (
<nav className="flex gap-4 text-sm opacity-80">
<span className="capitalize">{\` > \${category}\`}</span>
<span> / </span>
<span>{displayName}</span>
</nav>
);
}
`}
</Snippet>
<Text>
Now, Next.js will send the response as soon as it&apos;s ready on the
server. Also, as this is a page, we can define a{' '}
<Code>loading.js</Code> to display a skeleton while we fetch the
display name. You can find a working example in the{' '}
<Link href="/demo/pastries/key-lime-pie">
<Code>/demo</Code>
</Link>{' '}
route.
</Text>
</section>
</Page>
)
}
29 changes: 29 additions & 0 deletions solutions/parallel-routes-navbar/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "parallel-routes-navbar",
"repository": "https://github.com/vercel/examples.git",
"license": "MIT",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@vercel/examples-ui": "^2.0.1",
"next": "^13.4.10",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "^20.4.2",
"@types/react": "^18.2.15",
"autoprefixer": "^10.4.14",
"eslint": "^8.45.0",
"eslint-config-next": "^13.4.10",
"postcss": "^8.4.26",
"tailwindcss": "^3.3.3",
"turbo": "^1.10.8",
"typescript": "^5.1.6"
}
}
Loading