diff --git a/src/app/api/search/route.ts b/src/app/api/search/route.ts
index df889626..89faa89c 100644
--- a/src/app/api/search/route.ts
+++ b/src/app/api/search/route.ts
@@ -1,4 +1,24 @@
-import { source } from '@/lib/source';
-import { createFromSource } from 'fumadocs-core/search/server';
+import { programsSource, source } from "@/lib/source";
+import { createSearchAPI } from "fumadocs-core/search/server";
-export const { GET } = createFromSource(source);
+export const { GET } = createSearchAPI("advanced", {
+ language: "english",
+ indexes: [
+ ...source.getPages().map((page) => ({
+ title: page.data.title,
+ description: page.data.description,
+ url: page.url,
+ id: page.url,
+ structuredData: page.data.structuredData,
+ tag: "docs",
+ })),
+ ...programsSource.getPages().map((page) => ({
+ title: page.data.title,
+ description: page.data.description,
+ url: page.url,
+ id: page.url,
+ structuredData: page.data.structuredData,
+ tag: "programs",
+ })),
+ ],
+});
diff --git a/src/app/global.css b/src/app/global.css
index 369c9291..a073f9da 100644
--- a/src/app/global.css
+++ b/src/app/global.css
@@ -7,7 +7,7 @@
@theme inline {
--font-sans: var(--font-inter);
- --font-mono: var(--font-inter-mono);
+ --font-mono: var(--font-jetbrains-mono);
--font-funnel-display: var(--font-funnel-display);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
@@ -123,3 +123,7 @@
@apply bg-background text-foreground;
}
}
+
+kbd {
+ @apply font-mono;
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 8d897ffa..301cc8e7 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,19 +1,33 @@
+import SearchDialog from "@/components/search";
import { GoogleAnalytics } from "@next/third-parties/google";
import { RootProvider } from "fumadocs-ui/provider";
import type { Metadata } from "next";
-import { Funnel_Display, Inter } from "next/font/google";
+import { Funnel_Display, Inter, JetBrains_Mono } from "next/font/google";
import type { ReactNode } from "react";
import "./global.css";
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
+ weight: "variable",
+ display: "swap",
+ preload: true,
});
const funnelDisplay = Funnel_Display({
subsets: ["latin", "latin-ext"],
weight: "variable",
variable: "--font-funnel-display",
+ display: "swap",
+ preload: true,
+});
+
+const jetbrainsMono = JetBrains_Mono({
+ subsets: ["latin", "latin-ext"],
+ weight: "variable",
+ variable: "--font-jetbrains-mono",
+ display: "swap",
+ preload: true,
});
export const metadata: Metadata = {
@@ -26,13 +40,19 @@ export const metadata: Metadata = {
},
};
-export default function Layout({ children }: { children: ReactNode }) {
+export default function RootLayout({ children }: { children: ReactNode }) {
return (
- {children}
+
+ {children}
+
diff --git a/src/app/static.json/route.ts b/src/app/static.json/route.ts
new file mode 100644
index 00000000..88dfda3a
--- /dev/null
+++ b/src/app/static.json/route.ts
@@ -0,0 +1,72 @@
+import { programsSource, source } from "@/lib/source";
+import { StructuredData } from "fumadocs-core/mdx-plugins";
+import { type DocumentRecord } from "fumadocs-core/search/algolia";
+import { NextResponse } from "next/server";
+
+export const revalidate = false;
+
+// Type for page data with structured data
+interface PageWithStructuredData {
+ url: string;
+ data: {
+ title: string;
+ description?: string;
+ structuredData: StructuredData;
+ };
+}
+
+// Helper function to map pages to DocumentRecord format
+function mapPageToDocumentRecord(
+ page: PageWithStructuredData,
+ tag: string,
+): DocumentRecord {
+ return {
+ _id: page.url,
+ structured: page.data.structuredData,
+ url: page.url,
+ title: page.data.title,
+ description: page.data.description || "",
+ tag,
+ };
+}
+
+// Helper function to safely get pages and map them
+function mapPagesToDocuments(
+ sourceInstance: typeof source,
+ tag: string,
+): DocumentRecord[] {
+ try {
+ return sourceInstance
+ .getPages()
+ .map((page) => mapPageToDocumentRecord(page, tag));
+ } catch (error) {
+ console.error(`Error mapping ${tag} pages:`, error);
+ return [];
+ }
+}
+
+export function GET() {
+ try {
+ // Process both sources efficiently with better error handling
+ const docsPages = mapPagesToDocuments(source, "docs");
+ const programPages = mapPagesToDocuments(programsSource, "programs");
+
+ // Combine arrays efficiently
+ const results: DocumentRecord[] = [...docsPages, ...programPages];
+
+ // Add cache headers for better performance
+ return new NextResponse(JSON.stringify(results), {
+ status: 200,
+ headers: {
+ "Content-Type": "application/json",
+ "Cache-Control": "public, max-age=3600, stale-while-revalidate=86400",
+ },
+ });
+ } catch (error) {
+ console.error("Error generating static.json:", error);
+ return NextResponse.json(
+ { error: "Failed to generate search data" },
+ { status: 500 },
+ );
+ }
+}
diff --git a/src/components/search.tsx b/src/components/search.tsx
new file mode 100644
index 00000000..ebdfbe65
--- /dev/null
+++ b/src/components/search.tsx
@@ -0,0 +1,100 @@
+"use client";
+import { useDocsSearch } from "fumadocs-core/search/client";
+import {
+ SearchDialog,
+ SearchDialogClose,
+ SearchDialogContent,
+ SearchDialogFooter,
+ SearchDialogHeader,
+ SearchDialogIcon,
+ SearchDialogInput,
+ SearchDialogList,
+ SearchDialogOverlay,
+ type SharedProps,
+ TagsList,
+ TagsListItem,
+} from "fumadocs-ui/components/dialog/search";
+import { useI18n } from "fumadocs-ui/contexts/i18n";
+import { ArrowDownIcon, ArrowUpIcon, CornerDownLeftIcon } from "lucide-react";
+import { useState } from "react";
+
+const items = [
+ {
+ name: "All",
+ value: "",
+ },
+ {
+ name: "Docs",
+ description: "Only results about documentation",
+ value: "docs",
+ },
+ {
+ name: "Programming",
+ description: "Only results about programming languages",
+ value: "programs",
+ },
+];
+
+export default function CustomSearchDialog(props: SharedProps) {
+ const { locale } = useI18n(); // (optional) for i18n
+ const [tag, setTag] = useState();
+ const { search, setSearch, query } = useDocsSearch({
+ type: "fetch",
+ locale,
+ tag,
+ delayMs: 500,
+ });
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {items.map((item) => (
+
+ {item.name}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ Select
+
+
+
+ ESC
+
+ Close
+
+
+
+
+
+ );
+}