Skip to content

Commit 644dfd8

Browse files
committed
docs: add a 404 page
1 parent c8a356d commit 644dfd8

File tree

1 file changed

+171
-0
lines changed

1 file changed

+171
-0
lines changed

docs/app/not-found.tsx

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
"use client";
2+
3+
import { baseOptions } from "@/app/layout.config";
4+
import { Footer } from "@/components/Footer";
5+
import { HomeLayout } from "fumadocs-ui/layouts/home";
6+
import Link from "next/link";
7+
import { usePathname } from "next/navigation";
8+
import { useEffect, useState } from "react";
9+
import { FaBook, FaCode, FaSearch } from "react-icons/fa";
10+
11+
interface SearchResult {
12+
id: string;
13+
type: "page" | "heading" | "text";
14+
content: string;
15+
url: string;
16+
}
17+
18+
// Fallback popular pages when search doesn't return results
19+
const fallbackPages: SearchResult[] = [
20+
{
21+
id: "/docs/getting-started",
22+
type: "page",
23+
content: "Getting Started",
24+
url: "/docs/getting-started",
25+
},
26+
{
27+
id: "/docs/install",
28+
type: "page",
29+
content: "Installation",
30+
url: "/docs/install",
31+
},
32+
{
33+
id: "/examples/01-basic/01-minimal",
34+
type: "page",
35+
content: "Basic Example",
36+
url: "/examples/01-basic/01-minimal",
37+
},
38+
{
39+
id: "/docs/react/components",
40+
type: "page",
41+
content: "React Components",
42+
url: "/docs/react/components",
43+
},
44+
{
45+
id: "/docs/foundations/document-structure",
46+
type: "page",
47+
content: "Document Structure",
48+
url: "/docs/foundations/document-structure",
49+
},
50+
{
51+
id: "/docs/features/ai",
52+
type: "page",
53+
content: "AI Features",
54+
url: "/docs/features/ai",
55+
},
56+
];
57+
58+
export default function NotFound() {
59+
const pathname = usePathname();
60+
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
61+
const [isLoading, setIsLoading] = useState(true);
62+
63+
useEffect(() => {
64+
const searchSimilarPages = async () => {
65+
try {
66+
const pathSegments = pathname.split("/").filter(Boolean);
67+
const searchTerms = pathSegments.slice(-2).join(" ");
68+
69+
if (!searchTerms) {
70+
throw new Error("No search terms");
71+
}
72+
73+
const response = await fetch(
74+
`/api/search?query=${encodeURIComponent(searchTerms)}`,
75+
);
76+
const data = await response.json();
77+
78+
const pageResults = data
79+
.filter((item: SearchResult) => item.type === "page")
80+
.slice(0, 8);
81+
82+
setSearchResults(pageResults.length > 0 ? pageResults : fallbackPages);
83+
} catch (error) {
84+
setSearchResults(fallbackPages);
85+
} finally {
86+
setIsLoading(false);
87+
}
88+
};
89+
90+
const timer = setTimeout(searchSimilarPages, 100);
91+
return () => clearTimeout(timer);
92+
}, [pathname]);
93+
94+
const useFallback =
95+
searchResults.length === 0 || searchResults === fallbackPages;
96+
97+
return (
98+
<>
99+
<HomeLayout {...baseOptions}>
100+
<div className="mx-auto max-w-4xl pt-8">
101+
{/* 404 Header */}
102+
<div className="mb-12 text-center">
103+
<div className="mb-6 text-8xl font-bold text-gray-200 dark:text-gray-700">
104+
404
105+
</div>
106+
<h1 className="mb-4 text-4xl font-bold text-gray-900 dark:text-white">
107+
Page Not Found
108+
</h1>
109+
<p className="mx-auto max-w-2xl text-xl text-gray-600 dark:text-gray-300">
110+
We couldn't find the page you're looking for, but here are some
111+
pages that might help:
112+
</p>
113+
</div>
114+
115+
{/* Search Results */}
116+
<div className="mb-12">
117+
{isLoading ? (
118+
<div className="flex items-center justify-center space-x-3 py-12 text-gray-600 dark:text-gray-400">
119+
<div className="h-6 w-6 animate-spin rounded-full border-b-2 border-blue-600"></div>
120+
<span className="text-lg">Searching for similar pages...</span>
121+
</div>
122+
) : searchResults.length > 0 ? (
123+
<div className="space-y-6">
124+
<h2 className="mb-8 text-center text-2xl font-semibold text-gray-900 dark:text-white">
125+
{useFallback ? "Popular Pages" : "Similar Pages"}
126+
</h2>
127+
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
128+
{searchResults.map((result, index) => (
129+
<Link
130+
key={index}
131+
href={result.url}
132+
className="group block rounded-lg border border-gray-200 bg-white p-4 text-left transition-all duration-200 hover:border-blue-300 hover:shadow-md dark:border-gray-700 dark:bg-gray-800 dark:hover:border-blue-600"
133+
>
134+
<div className="flex items-center space-x-4">
135+
<div className="flex-shrink-0">
136+
{result.url.includes("/docs") ? (
137+
<FaBook className="h-5 w-5 text-blue-600 group-hover:text-blue-700" />
138+
) : result.url.includes("/examples") ? (
139+
<FaCode className="h-5 w-5 text-green-600 group-hover:text-green-700" />
140+
) : (
141+
<FaSearch className="h-5 w-5 text-gray-400 group-hover:text-gray-500" />
142+
)}
143+
</div>
144+
<div className="min-w-0 flex-1">
145+
<h3 className="text-lg font-medium text-gray-900 transition-colors group-hover:text-blue-600 dark:text-white dark:group-hover:text-blue-400">
146+
{result.content}
147+
</h3>
148+
<p className="mt-1 font-mono text-sm text-gray-400 dark:text-gray-500">
149+
{result.url}
150+
</p>
151+
</div>
152+
</div>
153+
</Link>
154+
))}
155+
</div>
156+
</div>
157+
) : (
158+
<div className="py-12 text-center text-gray-600 dark:text-gray-400">
159+
<p className="text-lg">
160+
No similar pages found. Try searching or browsing our
161+
documentation.
162+
</p>
163+
</div>
164+
)}
165+
</div>
166+
</div>
167+
</HomeLayout>
168+
<Footer />
169+
</>
170+
);
171+
}

0 commit comments

Comments
 (0)