35 | The useSearchParams hook returns a read only version of{' '}
36 | URLSearchParams. You can use{' '}
37 | useRouter() or <Link> to set new{' '}
38 | searchParams. After a navigation is performed, the current{' '}
39 | page.js will receive an updated searchParams{' '}
40 | prop.
41 |
72 | {option.items.map((item, i) => {
73 | const isActive =
74 | // set the first item as active if no search param is set
75 | (!searchParams[option.value] && i === 0) ||
76 | // otherwise check if the current item is the active one
77 | item === searchParams[option.value];
78 |
79 | // create new searchParams object for easier manipulation
80 | const params = new URLSearchParams(searchParams);
81 | params.set(option.value, item);
82 | return (
83 |
88 | {item}
89 |
90 | );
91 | })}
92 |
93 |
94 | );
95 | })}
96 |
97 |
98 |
99 |
100 | Docs
101 |
102 |
103 |
104 |
105 | );
106 | }
107 |
--------------------------------------------------------------------------------
/app/_streaming/_components/add-to-cart.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useRouter } from 'next/navigation';
4 | import { useTransition } from 'react';
5 | import { useCartCount } from './cart-count-context';
6 |
7 | export function AddToCart({ initialCartCount }: { initialCartCount: number }) {
8 | const router = useRouter();
9 | const [isPending, startTransition] = useTransition();
10 |
11 | const [, setOptimisticCartCount] = useCartCount();
12 |
13 | const addToCart = () => {
14 | setOptimisticCartCount(initialCartCount + 1);
15 |
16 | // update the cart count cookie
17 | document.cookie = `_cart_count=${initialCartCount + 1}; path=/; max-age=${
18 | 60 * 60 * 24 * 30
19 | }};`;
20 |
21 | // Normally you would also send a request to the server to add the item
22 | // to the current users cart
23 | // await fetch(`https://api.acme.com/...`);
24 |
25 | // Use a transition and isPending to create inline loading UI
26 | startTransition(() => {
27 | setOptimisticCartCount(null);
28 |
29 | // Refresh the current route and fetch new data from the server without
30 | // losing client-side browser or React state.
31 | router.refresh();
32 |
33 | // We're working on more fine-grained data mutation and revalidation:
34 | // https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions
35 | });
36 | };
37 |
38 | return (
39 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/app/_streaming/_components/cart-count-context.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React, { useState } from 'react';
4 |
5 | const CartCountContext = React.createContext<
6 | [number, React.Dispatch>] | undefined
7 | >(undefined);
8 |
9 | export function CartCountProvider({
10 | children,
11 | initialCartCount,
12 | }: {
13 | children: React.ReactNode;
14 | initialCartCount: number;
15 | }) {
16 | const [optimisticCartCount, setOptimisticCartCount] = useState(
17 | null,
18 | );
19 |
20 | const count =
21 | optimisticCartCount !== null ? optimisticCartCount : initialCartCount;
22 |
23 | return (
24 |
25 | {children}
26 |
27 | );
28 | }
29 |
30 | export function useCartCount() {
31 | const context = React.useContext(CartCountContext);
32 | if (context === undefined) {
33 | throw new Error('useCartCount must be used within a CartCountProvider');
34 | }
35 | return context;
36 | }
37 |
--------------------------------------------------------------------------------
/app/_streaming/_components/cart-count.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useCartCount } from './cart-count-context';
4 |
5 | export function CartCount() {
6 | const [count] = useCartCount();
7 | return {count};
8 | }
9 |
--------------------------------------------------------------------------------
/app/_streaming/_components/header.tsx:
--------------------------------------------------------------------------------
1 | import { NextLogoLight } from '#/ui/next-logo';
2 | import {
3 | MagnifyingGlassIcon,
4 | ShoppingCartIcon,
5 | } from '@heroicons/react/24/solid';
6 | import Image from 'next/image';
7 | import Link from 'next/link';
8 | import { CartCount } from './cart-count';
9 |
10 | export function Header() {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
50 |
51 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/app/_streaming/_components/pricing.tsx:
--------------------------------------------------------------------------------
1 | import type { Product } from '#/app/api/products/product';
2 | import { Ping } from '#/ui/ping';
3 | import { ProductEstimatedArrival } from '#/ui/product-estimated-arrival';
4 | import { ProductLowStockWarning } from '#/ui/product-low-stock-warning';
5 | import { ProductPrice } from '#/ui/product-price';
6 | import { ProductSplitPayments } from '#/ui/product-split-payments';
7 | import { ProductUsedPrice } from '#/ui/product-used-price';
8 | import { dinero, type DineroSnapshot } from 'dinero.js';
9 | import { Suspense } from 'react';
10 | import { AddToCart } from './add-to-cart';
11 |
12 | function LoadingDots() {
13 | return (
14 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/app/layouts/readme.mdx:
--------------------------------------------------------------------------------
1 | # Nested layouts
2 |
3 | A layout is UI that is shared between multiple routes. On navigation, layouts preserve state, remain interactive, and do not re-render.
4 |
5 | ### Demo
6 |
7 | 1. Try navigating between categories and sub categories above.
8 | 2. Notice layouts do not re-render (visualized as the a pink border animating to gray) when navigating across sibling routes.
9 | 3. Notice client state (visualized as a click counter) is preserved between navigations.
10 | 4. Notice you can interact (e.g. click the counter) with the layout while a sibling route is loading.
11 |
12 | ### Links
13 |
14 | - [Docs](https://nextjs.org/docs/app/getting-started/layouts-and-pages)
15 | - [Code](https://github.com/vercel/next-app-router-playground/tree/main/app/layouts)
16 |
--------------------------------------------------------------------------------
/app/loading/[section]/[category]/page.tsx:
--------------------------------------------------------------------------------
1 | import { notFound } from 'next/navigation';
2 | import { connection } from 'next/server';
3 | import { Boundary } from '#/ui/boundary';
4 | import { ProductCard } from '#/ui/new/product-card';
5 | import { getCategoryBySlug, getProductsByCategory } from '#/app/_internal/data';
6 |
7 | export default async function Page({
8 | params,
9 | }: {
10 | params: Promise<{ section: string; category: string }>;
11 | }) {
12 | // DEMO:
13 | // This page would normally be prerendered at build time because it doesn't use dynamic APIs.
14 | // That means the loading state wouldn't show. To force one:
15 | // 1. We indicate that we require a user Request before continuing:
16 | await connection();
17 | // 2. Add an artificial delay to make the loading state more noticeable:
18 | await new Promise((resolve) => setTimeout(resolve, 1000));
19 |
20 | const { category: categorySlug } = await params;
21 | const category = getCategoryBySlug(categorySlug);
22 | if (!category) {
23 | notFound();
24 | }
25 |
26 | const products = getProductsByCategory(category.id);
27 |
28 | return (
29 |
30 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/app/loading/page.tsx:
--------------------------------------------------------------------------------
1 | import { getProducts } from '#/app/_internal/data';
2 | import { Boundary } from '#/ui/boundary';
3 | import { ProductCard } from '#/ui/new/product-card';
4 | import { connection } from 'next/server';
5 |
6 | export default async function Page() {
7 | // DEMO:
8 | // This page would normally be prerendered at build time because it doesn't use dynamic APIs.
9 | // That means the loading state wouldn't show. To force one:
10 | // 1. We indicate that we require a user Request before continuing:
11 | await connection();
12 | // 2. Add an artificial delay to make the loading state more noticeable:
13 | await new Promise((resolve) => setTimeout(resolve, 1000));
14 |
15 | const products = getProducts({ limit: 9 });
16 |
17 | return (
18 |
19 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/app/loading/readme.mdx:
--------------------------------------------------------------------------------
1 | # Loading.js
2 |
3 | `loading.js` is a file convention that lets you define fallback UI for a route segment when it's loading, enabling prefetching and instant navigation for dynamic routes.
4 |
5 | When a user navigates to a dynamic route with prefetched fallback UI, the URL updates immediately and the loading state is shown while the segment loads.
6 |
7 | Use filesytem hierarchy to define more or less specific fallback UI.
8 |
9 | ### Demo
10 |
11 | - Navigate between categories using the menu above.
12 | - **Instant navigation**: Even though the new route segment is still rendering on the server, we can instantly navigate (update the url and render fallback UI) to the new route. The actual segment content streams in once rendering is complete.
13 |
14 | #### Notes
15 |
16 | - An artificial delay is added to pages to simulate a slow data request and make the loading state more noticeable.
17 |
18 | ### Links
19 |
20 | - [Docs](https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming)
21 | - [Code](https://github.com/vercel/next-app-router-playground/tree/main/app/loading)
22 |
--------------------------------------------------------------------------------
/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | import { Boundary } from '#/ui/boundary';
2 |
3 | export default function NotFound() {
4 | return (
5 |
6 |
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/app/parallel-routes/readme.mdx:
--------------------------------------------------------------------------------
1 | # Parallel Routes
2 |
3 | - Parallel Routes allow you to simultaneously or conditionally render
4 | multiple pages, with independent navigation, in the same layout.
5 | - Parallel Routes can be used for advanced routing patterns like [Conditional Routes](https://nextjs.org/docs/app/building-your-application/routing/parallel-routes#conditional-routes) and [Intercepted Routes](https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes).
6 | - Try using the tabs in one parallel route to navigate. Notice the URL
7 | changes but the unaffected parallel route is preserved.
8 | - Try using the browser's backwards and forwards navigation.
9 | Notice the browser's URL history state and active UI state is
10 | correctly synced.
11 | - Try navigating to a tab in one parallel route and refreshing the
12 | browser. Notice you can choose what UI to show parallel routes that
13 | don't match the initial URL.
14 |
15 | ### Links
16 |
17 | - [Docs](https://nextjs.org/docs/app/building-your-application/routing/parallel-routes)
18 | - [Code](https://github.com/vercel/next-app-router-playground/tree/main/app/parallel-routes)
19 |
--------------------------------------------------------------------------------
/app/route-groups/(checkout)/checkout/page.tsx:
--------------------------------------------------------------------------------
1 | import { getDemoMeta } from '#/app/_internal/demos';
2 | import { Boundary } from '#/ui/boundary';
3 | import { Tab } from '#/ui/tabs';
4 |
5 | const demo = getDemoMeta('route-groups');
6 |
7 | export default function Page() {
8 | return (
9 |
10 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/app/use-link-status/page.tsx:
--------------------------------------------------------------------------------
1 | import { getProducts } from '#/app/_internal/data';
2 | import { Boundary } from '#/ui/boundary';
3 | import { ProductCard } from '#/ui/new/product-card';
4 | import { connection } from 'next/server';
5 |
6 | export default async function Page() {
7 | // DEMO:
8 | // This page would normally be prerendered at build time because it doesn't use dynamic APIs.
9 | // That means the loading state wouldn't show. To force one:
10 | // 1. We indicate that we require a user Request before continuing:
11 | await connection();
12 | // 2. Add an artificial delay to make the loading state more noticeable:
13 | await new Promise((resolve) => setTimeout(resolve, 1000));
14 |
15 | const products = getProducts({ limit: 9 });
16 |
17 | return (
18 |
19 |