In Part 1 we created the foundation of our Next.js 14 project. In Part 2 we created the frontend with Contentful as headless CMS, Tailwindcss for styling, and Typescript for the coding. In Part 3 we added a header and a footer component, as well as the so-called draft/preview functionality offered by Contentful, and last but not least the nowadays mandatory dark mode toggle. In Part 4 we are going to add a custom 404 page, a loading UI (SVG Spinner) and we add some extra colors to our tailwind.config.ts
Here already a spoiler to the final result of Part 4 -> https://nextjs14-full-example-404-loading.vercel.app/

Custom 404 / Not Found Page
Let’s create a new file “not-found.tsx” under /src/app with our custom wording/styling
import { Container } from "@/components/contentful/container/Container";
function NotFoundPage() {
return (
<>
<div className="min-h-full px-4 py-4 sm:px-6 sm:py-24 md:grid md:place-items-center lg:px-8">
<div className="mx-auto max-w-max">
<Container className="mt-5">
<div className="flex mt-6">
<p className="text-4xl font-extrabold text-blue600 sm:text-5xl">
Ups
</p>
<div className="ml-6">
<div className="pl-6 border-l border-gray500">
<h2 className="text-3xl font-bold tracking-tight text-gray900 dark:text-white sm:text-4xl">
Something went wrong! - Etwas ist schief gelaufen !
</h2>
<p className="mt-1 text-lg text-gray500 dark:text-white">
Please select a topic from the tag cloud above or go back
home
</p>
<p className="mt-1 text-lg text-gray500 dark:text-white">
Bitte wählen Sie ein Thema aus der Cloud oben oder klicken
auf Go back home Button
</p>
</div>
<div className="flex mt-10 space-x-3 sm:pl-6">
<a
href="/"
className="inline-flex items-center px-6 py-4 text-sm text-4xl font-medium text-white bg-blue600 border border-transparent rounded-md shadow-sm hover:bg-blue700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue500"
>
Go back home
</a>
</div>
</div>
</div>
</Container>
</div>
</div>
</>
);
}
export default NotFoundPage;If you click “About” in the upper left corner of https://nextjs14-full-example-404-loading.vercel.app/ you will see our custom 404 page.

Instead of the default one

Custom UI Loading
Let’s go ahead with the new file loading.tsx, which I created under /src/app
export default function loading() {
return (
<div className="pt-4 text-center">
<div role="status">
<svg
aria-hidden="true"
className="inline w-10 h-10 mr-2 text-gray200 animate-spin dark:text-gray600 fill-blue600"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
<span className="sr-only">Loading...</span>
</div>
</div>
);
}Since the loading spinner is only visible during the initial loading state, I created my subpage called “spinner” -> https://nextjs14-full-example-404-loading.vercel.app/spinner so that you can see it in action.

The special file loading.tsx helps you create meaningful Loading UI with React Suspense. With this convention, you can show an instant loading state from the server while the content of a route segment loads. The new content is automatically swapped in once rendering is complete. It’s a great possibility to enhance the UX.
Extra Colors in tailwind.config.ts
I added these colors in the “theme” section within tailwind.config.ts
white: extraColors.white,
black: extraColors.black,
indigo: extraColors.indigo,console.log("extracolors", extraColors);shows the color names with their corresponding “Hex-Value”, which I imported from the package “tailwindcss/colors”

I imported other colors from the package “@contentful/f36-tokens” as well.
import tokens from "@contentful/f36-tokens";
const colors = Object.entries(tokens).reduce(
(acc: Record<string, any>, [key, value]) => {
// Filter Hex colors from the f36-tokens
if (/^#[0-9A-F]{6}$/i.test(value as any)) {
acc[key] = value;
}
return acc;
},
{} as Record<string, string>
);console.log("colors", colors);
Complete tailwind.config.ts with the integration of “extracolors” from tailwindcss/colors and “colors” via tokens from “@contentful/f36-tokens”
import type { Config } from "tailwindcss";
import tokens from "@contentful/f36-tokens";
const { fontFamily } = require("tailwindcss/defaultTheme");
const extraColors = require("tailwindcss/colors"); //for extra colors e.g. in the Arctile TOC -> emerald
const colors = Object.entries(tokens).reduce(
(acc: Record<string, any>, [key, value]) => {
// Filter Hex colors from the f36-tokens
if (/^#[0-9A-F]{6}$/i.test(value as any)) {
acc[key] = value;
}
return acc;
},
{} as Record<string, string>
);
const config: Config = {
darkMode: "class",
content: [
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
colors,
extend: {
colors: {
emerald: extraColors.emerald,
white: extraColors.white,
black: extraColors.black,
indigo: extraColors.indigo,
},
maxWidth: {
"8xl": "90rem",
},
letterSpacing: {
snug: "-0.011em",
},
boxShadow: {
// light
"tremor-input": "0 1px 2px 0 rgb(0 0 0 / 0.05)",
"tremor-card":
"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
"tremor-dropdown":
"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
},
borderRadius: {
"tremor-small": "0.375rem",
"tremor-default": "0.5rem",
"tremor-full": "9999px",
},
fontSize: {
"2xs": "0.625rem",
"3xl": "1.75rem",
"4xl": "2.5rem",
"tremor-label": "0.75rem",
"tremor-default": ["1.0rem", { lineHeight: "1.25rem" }],
"tremor-title": ["1.125rem", { lineHeight: "1.75rem" }],
"tremor-metric": ["1.875rem", { lineHeight: "2.25rem" }],
},
lineHeight: {
tighter: "1.1",
},
fontFamily: {
sans: ["var(--font-urbanist)", ...fontFamily.sans],
},
},
},
plugins: [require("@tailwindcss/typography")],
};
export default config;Since the names of the colors were not “gray-600” or “blue-600”, but “gray600” and “blue600” without the dash I had to fix the className styles in my tsx files. That’s the reason why a lot of files were modified. So let’s sum up.
Changed/added files

Modified Files Vercel project URL ->
https://nextjs14-full-example-404-loading.vercel.app/
Github Repo with full code
https://github.com/cloudapp-dev/nextjs14-typescript-app-router-contentful/tree/nextjs14-part4
If you like what you see, then please support me with a clap or follow me on medium.com.



