Routing

Split the app into routes with a shared layout, links with shortcuts and history.

This chapter's app is running in your console. Open it with ⌘⌥JCtrl+Shift+J, then try it.
Watch the chapter3:46 · narrated · captions

In this chapter

  • Files are routes
  • A shared layout
  • Going back
  • Dynamic segments

The App Router maps files in app/ to URLs, exactly like Next.js. The browser's address bar and history stay in sync, so back, forward and deep links work while the UI renders in the console.

Files are routes

Move the list into its own route and add a settings page:

app/+5text
app/  layout.tsx  page.tsx                 → /Added:   deployments/Added:     page.tsx               → /deploymentsAdded:     [id]/page.tsx          → /deployments/dpl_8fK2Added:   settings/Added:     page.tsx               → /settings

A shared layout

layout.tsx wraps every page and keeps its state across navigation. Put the top bar and the navigation in it.

app/layout.tsx+25tsx
Added: import type { ReactNode } from "react";Added: import { Box, Spacer, Text } from "@textjs/next";Added: import Link from "@textjs/next/link";Added: import { usePathname } from "@textjs/next/navigation";Added: Added: export const metadata = { title: { default: "Acme Deploys", template: "%s · Acme" } };Added: Added: export default function RootLayout({ children }: { children?: ReactNode }) {Added:   const pathname = usePathname();Added:   return (Added:     <Box className="flex-col grow">Added:       <Box className="flex-row h-1 px-1 gap-2">Added:         <Text bold>▼ Acme</Text>Added:         <Text color="subtle">{`acme.local${pathname === "/" ? "" : pathname}`}</Text>Added:         <Spacer />Added:       </Box>Added:       <Box className="flex-row h-1 px-1 gap-3">Added:         <Link href="/" shortcut="1">Overview</Link>Added:         <Link href="/deployments" shortcut="2">Deployments</Link>Added:         <Link href="/settings" shortcut="3">Settings</Link>Added:       </Box>Added:       <Box className="flex-col grow p-1">{children}</Box>Added:     </Box>Added:   );Added: }

shortcut binds a single key to the link. Press 1, 2 and 3 in the console; the URL of a real app would change with each one.

Going back

useRouter() navigates from code. Wire Backspace to router.back() in the layout:

app/layout.tsx+7-2tsx
Removed: import { Box, Spacer, Text } from "@textjs/next";Added: import { Box, Spacer, Text, useInput } from "@textjs/next";import Link from "@textjs/next/link";Removed: import { usePathname } from "@textjs/next/navigation";Added: import { usePathname, useRouter } from "@textjs/next/navigation";export default function RootLayout({ children }: { children?: ReactNode }) {  const pathname = usePathname();Added:   const router = useRouter();Added: Added:   useInput((e) => {Added:     if (e.key === "backspace") router.back();Added:   });

Dynamic segments

[id] folders receive their value in params:

app/deployments/[id]/page.tsxtsx
import { Text, type PageProps } from "@textjs/next";export default function DeploymentPage({ params }: PageProps<{ id: string }>) {  return <Text bold>{params.id}</Text>;}

Static hosts need a single SPA rewrite to serve deep links. Chapter 10 covers it.

Try it

This is Acme Deploys at the end of the chapter, running in DevTools. Click the pad, then use the keys below.

▼ 05 · Routing

Open the console

⌘⌥J
CtrlShiftJ

The app renders in DevTools. Then click here and press 2.

123navigate⌫back

Open on a desktop

This demo renders in the Chrome DevTools console, which needs a desktop browser.

Quiz

2 questions

Answer every question to complete the chapter.

Which file renders at /deployments?

 

What survives navigation between pages?