Fuma studio

Manual Setup

Adding Fumastudio to existing Nextjs project.

Getting started

Install the necessary dependencies, asssuming you are using Shadcn with Nextjs.

npm i @trythis/js-schema @trythis/nextjs

Loader configuration

The loader function reads the Bruno collection converting each request to appropriate documentation.

lib/source.ts
import { httpCodes } from "@/lib/http-codes";
import { loader, snippet, toBrunoDocs } from "@trythis/nextjs";
import { join } from "node:path";

const cwd = process.cwd();
const docsDir = join(cwd, "collections");

export const source = loader({
    baseURL: "/api-reference",
    source: await toBrunoDocs({ cwd: docsDir }),
    languages: [
        snippet.curl({ target: "shell", syntax: "bash", label: "cURL" }),
        snippet.axios({ target: "node", syntax: "typescript" }),
        snippet.fetch({ target: "node", syntax: "typescript" }),
    ],
    variables: {
        // your variables go here (it respects the same hierarchy as the Bruno Desktop/CLI app)
    },
    httpCodes,
});

Server-only

This file is supposed to run on the server, so do not place client code in here.

MDX components

Configure the MDX components which would render the Bruno collection requests. At the root of your Nextjs project.

mdx-components.tsx
import { APIPage } from "@/components/fumastudio/api-page";
import * as AllLucideIcons from "lucide-react";

type MDXComponents = any;

export const useIcons = (): any => {
	return AllLucideIcons;
};

const components: MDXComponents = {
	ApiPage: APIPage,
};

// Override or add your mdx components here
export function useMDXComponents(
	defaultComponents?: MDXComponents,
): MDXComponents {
	return {
		...defaultComponents,
		...components,
	};
}

API Page configuration

Assuming your docs are accessible at /api-reference, in your nextjs app router. Create a corresponding page.tsx file.

app/api-reference/[[...slug]]/page.tsx
import { ConfigProvider } from "@/components/fumastudio/config-provider";
import { PageTree } from "@/components/fumastudio/page-tree";
import { SearchDialog } from "@/components/fumastudio/search-dialog";
import { ThemeSelector } from "@/components/fumastudio/theme-selector";
import { Separator } from "@/components/ui/separator";
import {
	SidebarInset,
	SidebarProvider,
	SidebarTrigger,
} from "@/components/ui/sidebar";
import { source } from "@/lib/source";
import { useMDXComponents } from "@/mdx-components";
import { config } from "@/source.config";
import { notFound } from "next/navigation";

type PageParams = { slug: string[] };

type PageProps = {
	params: Promise<PageParams>;
};

export default async function Page(props: PageProps) {
	const params = await props.params;
	const request = source.request(params.slug);

	if (!request) {
		return notFound();
	}

	const pageTree = source.getPageTree();

	const schemes = await source.getSchemes(params.slug);

	const MDX = await source.getPage(params.slug, {
		schemes,
		components: useMDXComponents(),
	});

	if (!MDX) {
		return notFound();
	}

	return (
		<SidebarProvider>
			<PageTree tree={pageTree} />

			<SidebarInset>
				<div className="flex items-center h-14 gap-2 px-4 border-b shrink-0">
					<div className="flex items-center">
						<SidebarTrigger className="ml-1.5" />
						<Separator
							orientation="vertical"
							className="mr-2 data-[orientation=vertical]:h-4"
						/>
					</div>

					<div className="flex items-center ml-auto">
						<SearchDialog />
						<ThemeSelector />
					</div>
				</div>

				<ConfigProvider config={config}>
					<div className="flex flex-col flex-1 gap-4 pt-24 mx-auto w-full max-w-[70rem] px-16 md:max-w-[60rem] md:px-24">
						{MDX}
					</div>
				</ConfigProvider>
			</SidebarInset>
		</SidebarProvider>
	);
}

export async function generateStaticParams() {
	return source.generateParams();
}

export async function generateMetadata(props: PageProps) {
	const params = await props.params;
	const entry = await source.getFrontmatter(params.slug);

	return entry;
}

API Proxy configuration

When mocking external APIs in the browser, you may run into CORS errors. To avoid this, requests are proxied through your Next.js server so they are executed server-side and forwarded to the intended endpoint. This utility automatically creates route handlers for all standard HTTP methods.

app/api/proxy/route.ts
import { createProxy } from "@trythis/nextjs/proxy";

export const { GET, HEAD, PUT, POST, PATCH, DELETE } = createProxy();

ORPC configuration

ORPC provides a type-safe REST API layer built on top of your router definitions. This setup connects your ORPC router to a Next.js route handler using the Fetch API.

app/api/orpc/[[...rest]]/route.ts
import { router } from "@/orpc-setup/index";
import { onError } from "@orpc/server";
import { RPCHandler } from "@orpc/server/fetch";

const handler = new RPCHandler(router, {
	interceptors: [
		onError((error) => {
			console.error(error);
		}),
	],
});

async function handleRequest(request: Request) {
	const { response } = await handler.handle(request, {
		prefix: "/api/orpc",
		context: {},
	});

	return response ?? new Response("Not found", { status: 404 });
}

export const HEAD = handleRequest;
export const GET = handleRequest;
export const POST = handleRequest;
export const PUT = handleRequest;
export const PATCH = handleRequest;
export const DELETE = handleRequest;

Install UI components

Fumastudio ships shadcn compatible components which can easily be installed via the shadcn cli.

npx shadcn@latest add https://fumastudio.com/r/api-page https://fumastudio.com/r/http-codes

See the components docs for more info.