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⌘⌥KCtrl+Shift+K⌘⌥C (turn on developer features first), then try it.
Watch the chapter3:46

In this chapter

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

The App Router maps files in app/ to URLs, following the Next.js App Router conventions. 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/app";Added: import Link from "@textjs/app/link";Added: import { usePathname } from "@textjs/app/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/app";Added: import { Box, Spacer, Text, useInput } from "@textjs/app";import Link from "@textjs/app/link";Removed: import { usePathname } from "@textjs/app/navigation";Added: import { usePathname, useRouter } from "@textjs/app/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/app";export default function DeploymentPage({ params }: PageProps<{ id: string }>) {  return <Text bold>{params.id}</Text>;}

On a static host, dynamic segments like this one need a rewrite to index.html. 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
⌘⌥K
CtrlShiftK
⌘⌥C

The app renders in DevTools.First turn on “Show features for web developers” in Safari Settings → Advanced. Then click here and press 2.

Open on a desktop

This app renders in the 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?