diff --git a/surfsense_web/.gitignore b/surfsense_web/.gitignore
index e72b4d6a4..c78e9de6c 100644
--- a/surfsense_web/.gitignore
+++ b/surfsense_web/.gitignore
@@ -39,3 +39,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+
+# source
+/.source/
diff --git a/surfsense_web/app/api/search/route.ts b/surfsense_web/app/api/search/route.ts
new file mode 100644
index 000000000..01401b7f5
--- /dev/null
+++ b/surfsense_web/app/api/search/route.ts
@@ -0,0 +1,4 @@
+import { source } from '@/lib/source';
+import { createFromSource } from 'fumadocs-core/search/server';
+
+export const { GET } = createFromSource(source);
\ No newline at end of file
diff --git a/surfsense_web/app/docs/[[...slug]]/page.tsx b/surfsense_web/app/docs/[[...slug]]/page.tsx
new file mode 100644
index 000000000..6c8574d87
--- /dev/null
+++ b/surfsense_web/app/docs/[[...slug]]/page.tsx
@@ -0,0 +1,46 @@
+import { source } from '@/lib/source';
+import {
+ DocsBody,
+ DocsDescription,
+ DocsPage,
+ DocsTitle,
+} from 'fumadocs-ui/page';
+import { notFound } from 'next/navigation';
+import { getMDXComponents } from '@/mdx-components';
+
+export default async function Page(props: {
+ params: Promise<{ slug?: string[] }>;
+}) {
+ const params = await props.params;
+ const page = source.getPage(params.slug);
+ if (!page) notFound();
+
+ const MDX = page.data.body;
+
+ return (
+
+ {page.data.title}
+ {page.data.description}
+
+
+
+
+ );
+}
+
+export async function generateStaticParams() {
+ return source.generateParams();
+}
+
+export async function generateMetadata(props: {
+ params: Promise<{ slug?: string[] }>;
+}) {
+ const params = await props.params;
+ const page = source.getPage(params.slug);
+ if (!page) notFound();
+
+ return {
+ title: page.data.title,
+ description: page.data.description,
+ };
+}
\ No newline at end of file
diff --git a/surfsense_web/app/docs/layout.tsx b/surfsense_web/app/docs/layout.tsx
new file mode 100644
index 000000000..e818c1f68
--- /dev/null
+++ b/surfsense_web/app/docs/layout.tsx
@@ -0,0 +1,12 @@
+import { source } from '@/lib/source';
+import { DocsLayout } from 'fumadocs-ui/layouts/docs';
+import type { ReactNode } from 'react';
+import { baseOptions } from '@/app/layout.config';
+
+export default function Layout({ children }: { children: ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
\ No newline at end of file
diff --git a/surfsense_web/app/globals.css b/surfsense_web/app/globals.css
index 8fdefeca5..98e4411fb 100644
--- a/surfsense_web/app/globals.css
+++ b/surfsense_web/app/globals.css
@@ -1,4 +1,6 @@
-@import "tailwindcss";
+@import 'tailwindcss';
+@import 'fumadocs-ui/css/neutral.css';
+@import 'fumadocs-ui/css/preset.css';
@plugin "tailwindcss-animate";
diff --git a/surfsense_web/app/layout.config.tsx b/surfsense_web/app/layout.config.tsx
new file mode 100644
index 000000000..ef0500157
--- /dev/null
+++ b/surfsense_web/app/layout.config.tsx
@@ -0,0 +1,7 @@
+import { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
+
+export const baseOptions: BaseLayoutProps = {
+ nav: {
+ title: 'SurfSense Documentation',
+ },
+};
\ No newline at end of file
diff --git a/surfsense_web/app/layout.tsx b/surfsense_web/app/layout.tsx
index be0b18079..6b60891a4 100644
--- a/surfsense_web/app/layout.tsx
+++ b/surfsense_web/app/layout.tsx
@@ -5,6 +5,7 @@ import { Roboto } from "next/font/google";
import { Toaster } from "@/components/ui/sonner";
import { ThemeProvider } from "@/components/theme/theme-provider";
+import { RootProvider } from 'fumadocs-ui/provider';
const roboto = Roboto({
subsets: ["latin"],
@@ -64,8 +65,10 @@ export default async function RootLayout({
disableTransitionOnChange
defaultTheme="light"
>
- {children}
-
+
+ {children}
+
+