Practice: Custom Error Page, Fonts, YouTube Embed
app/not-found.tsx handles missing routes. app/error.tsx is a Client Component error boundary for unexpected failures. app/global-error.tsx covers root layout failures.
Custom error page
app/not-found.tsx handles missing routes. app/error.tsx is a Client Component error boundary for unexpected failures. app/global-error.tsx covers root layout failures.
Real-life example: not-found is a friendly "wrong floor" sign. error.tsx is the building fire warden telling you calmly what to do next.
import Link from "next/link";
export default function NotFound() {
return (
<main>
<h1>Page not found</h1>
<p>That lesson may have moved. Try the course home.</p>
<Link href="/learn">Back to Learn</Link>
</main>
);
}"use client";
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<main>
<h1>Something went wrong</h1>
<p>{error.message}</p>
<button type="button" onClick={reset}>
Try again
</button>
</main>
);
}Custom local fonts recap
Use next/font/local for self-hosted files. Fonts are optimized and avoid layout shift when configured with display: swap.
Real-life example: Self-hosting fonts is stocking your own kitchen spices — you are not waiting for a neighbor to lend garam masala on every cook.
import localFont from "next/font/local";
const brandSans = localFont({
src: [
{ path: "../fonts/BrandSans-Regular.woff2", weight: "400" },
{ path: "../fonts/BrandSans-Bold.woff2", weight: "700" },
],
variable: "--font-brand",
display: "swap",
});
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={brandSans.variable}>
<body>{children}</body>
</html>
);
}YouTube embed — safe pattern
Do not paste raw iframes with autoplay spam. Use youtube-nocookie.com, a title for accessibility, and lazy loading. Wrap in an aspect-ratio box.
Real-life example: A careful YouTube embed is inviting a guest speaker with a scheduled slot — not letting them rearrange your whole living room.
type Props = { videoId: string; title: string };
export function YouTubeEmbed({ videoId, title }: Props) {
return (
<div style={{ aspectRatio: "16 / 9", width: "100%" }}>
<iframe
src={`https://www.youtube-nocookie.com/embed/${videoId}`}
title={title}
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
style={{ width: "100%", height: "100%", border: 0 }}
/>
</div>
);
}
// <YouTubeEmbed videoId="dQw4w9WgXcQ" title="Intro to App Router" />