Skip to main content
ESC

App Organization

Robindoc can be considered to consist of several parts:

Organizing Documentation Files 

One of the main advantages of Robindoc is that documentation files can reside in any source — whether they are files outside the current directory or a remote git repository (more about sources on the "Data Source" page).

Robindoc’s main approach is that you don’t adjust the location of markdown files for Robindoc; instead, Robindoc builds the site from your markdown files. In other words, you place files so that they can be read in GitHub, and Robindoc serves as a convenient interface.

However, when using the automatic mode for generating the structure, the documentation file structure should match the desired structure on the site. In manual mode, you can organize the documentation files as you wish, but it is still recommended to reflect the site’s structure.

Application Setup and Configuration 

You can initialize Robindoc on any subpath of your site, as long as you specify the basePath in the project initialization and pass the correct path in the Robindoc components.

After initialization, you will receive Sidebar, Page, getStaticParams, getMetadata, and getPageData. Read more on the Initialization page.

Global elements - RobinProvider, Header, Footer, Containers and Sidebar - should ideally be placed above all pages and reused across all. Currently, Robindoc works only with the App Router. Once RSC is available for the Pages Router, Robindoc will automatically support it as well.

Page Setup 

Next.js supports dynamic routes, so it is recommended to set up one dynamic segment  for all documentation pages.

v14 TSX
v14 JSX
v15 TSX
v15 JSX
import { Page, Sidebar, getMetadata, getStaticParams } from "../robindoc";

const Page: React.FC<{ params }: { params: { segments?: string[] } }> = async ({ params }) => {
const pathname = "/docs/" + (params.segments?.join("/") || "");

return <Page pathname={pathname} />;
};

export default Page;
import { Page, Sidebar, getMetadata, getStaticParams } from "../robindoc";

const Page = async ({ params }) => {
const pathname = "/docs/" + (params.segments?.join("/") || "");

return <Page pathname={pathname} />;
};

export default Page;
import { Page, Sidebar, getMetadata, getStaticParams } from "../robindoc";

const Page: React.FC<{ params }: { params: Promise<{ segments?: string[] }> }> = async ({ params }) => {
const { segments } = await params;
const pathname = "/docs/" + (segments?.join("/") || "");

return <Page pathname={pathname} />;
};

export default Page;
import { Page, Sidebar, getMetadata, getStaticParams } from "../robindoc";

const Page = async ({ params }) => {
const { segments } = await params;
const pathname = "/docs/" + (segments?.join("/") || "");

return <Page pathname={pathname} />;
};

export default Page;

For more details about the props, refer to the Page element page.

You should also set up metadata generation and static parameters generation (if you want to use SSG, which is highly recommended):

v14 TSX
v14 JSX
v15 TSX
v15 JSX
import { Page, Sidebar, getMetadata, getStaticParams } from "../robindoc";

// ...

export const generateMetadata = async ({
params,
}: {
params: { segments?: string[] };
}) => {
const pathname = "/docs/" + (params.segments?.join("/") || "");
const metadata = await getMetadata(pathname);

return metadata;
};

export const generateStaticParams = async () => {
const staticParams = await getStaticParams("/docs/");
return staticParams;
};
import { Page, Sidebar, getMetadata, getStaticParams } from "../robindoc";

// ...

export const generateMetadata = async ({ params }) => {
const pathname = "/docs/" + (params.segments?.join("/") || "");
const metadata = await getMetadata(pathname);

return metadata;
};

export const generateStaticParams = async () => {
const staticParams = await getStaticParams("/docs/");
return staticParams;
};
import { Page, Sidebar, getMetadata, getStaticParams } from "../robindoc";

// ...

export const generateMetadata = async ({
params,
}: {
params: Promise<{ segments?: string[] }>;
}) => {
const { segments } = await params;
const pathname = "/docs/" + (segments?.join("/") || "");
const metadata = await getMetadata(pathname);

return metadata;
};

export const generateStaticParams = async () => {
const staticParams = await getStaticParams("/docs/");
return staticParams;
};
import { Page, Sidebar, getMetadata, getStaticParams } from "../robindoc";

// ...

export const generateMetadata = async ({ params }) => {
const { segments } = await params;
const pathname = "/docs/" + (segments?.join("/") || "");
const metadata = await getMetadata(pathname);

return metadata;
};

export const generateStaticParams = async () => {
const staticParams = await getStaticParams("/docs/");
return staticParams;
};

Robindoc Setup 

It is recommended to place the Robindoc initialization near this route.

TypeScript
JavaScript
import { initializeRobindoc } from "robindoc";

export const { Page, Sidebar, getStaticParams, getMetadata, getPageData } =
initializeRobindoc({
configuration: {
sourceRoot: "../docs",
basePath: "/docs",
gitToken: "YOUR_TOKEN",
fetcher: (url, init) =>
fetch(url, { ...init, cache: "force-cache", next: { tags: ["docs"] } }),
},
items: "auto",
});
import { initializeRobindoc } from "robindoc";

export const { Page, Sidebar, getStaticParams, getMetadata, getPageData } =
initializeRobindoc({
configuration: {
sourceRoot: "../docs",
basePath: "/docs",
gitToken: "YOUR_TOKEN",
fetcher: (url, init) =>
fetch(url, { ...init, cache: "force-cache", next: { tags: ["docs"] } }),
},
items: "auto",
});
When uploading to Vercel, the final image will contain only files inside the next.js project

Layout Setup 

The Next.js Layout should be placed one level up so that it remains static for all pages.

TypeScript
JavaScript
import { RobinProvider, Header, Footer, DocsContainer } from "robindoc";
import { Sidebar } from "./robindoc";
import Logo from "./logo";

import "robindoc/lib/styles.css";

const Layout: React.FC<React.PropsWithChildren> = ({ children }) => (
<RobinProvider>
<Header logo={<Logo />} />
<DocsContainer>
<Sidebar />
{children}
</DocsContainer>
<Footer copyright="© 2024 All rights reserved" />
</RobinProvider>
);

export default Layout;
import { RobinProvider, Header, Footer, DocsContainer } from "robindoc";
import { Sidebar } from "./robindoc";
import Logo from "./logo";

import "robindoc/lib/styles.css";

const Layout = ({ children }) => (
<RobinProvider>
<Header logo={<Logo />} />
<DocsContainer>
<Sidebar />
{children}
</DocsContainer>
<Footer copyright="© 2024 All rights reserved" />
</RobinProvider>
);

export default Layout;

For more details on configuring elements, refer to the RobinProvider, Header, Sidebar, Footer, and Containers block pages.

Search Setup 

If you want to enable search, you can create your own API route and pass the path to it in your Header.

TypeScript
JavaScript
export const GET = async (request: Request) => {
const url = new URL(request.url);
const search = url.searchParams.get("s");

const headers = new Headers();
headers.set("Content-Type", "application/json; charset=UTF-8");

if (!search) return new Response(JSON.stringify([]), { headers });

const searchResults = await advancedSearcher(search);

return new Response(JSON.stringify(searchResults), { headers });
};
export const GET = async (request) => {
const url = new URL(request.url);
const search = url.searchParams.get("s");

const headers = new Headers();
headers.set("Content-Type", "application/json; charset=UTF-8");

if (!search) return new Response(JSON.stringify([]), { headers });

const searchResults = await advancedSearcher(search);

return new Response(JSON.stringify(searchResults), { headers });
};
TypeScript
JavaScript
const Layout: React.FC<React.PropsWithChildren> = ({ children }) => (
<RobinProvider>
<Header logo={<Logo />} searcher="/api/search" />
{/* ... */}
</RobinProvider>
);

export default Layout;
const Layout = ({ children }) => (
<RobinProvider>
<Header logo={<Logo />} searcher="/api/search" />
{/* ... */}
</RobinProvider>
);

export default Layout;

Vercel 

Since the image in Vercel does not include indirect files - for working with documentation on the server - local documentation files need to be passed explicitly via outputFileTracingIncludes config.

v14 TSX
v14 JSX
v15 TSX
v15 JSX
/** @type {import("next").NextConfig} */
const nextConfig = {
experimental: {
outputFileTracingIncludes: {
"/api/search": ["./docs/**/*", "./blog/**/*", "./README.md"],
},
},
};
/** @type {import("next").NextConfig} */
const nextConfig = {
experimental: {
outputFileTracingIncludes: {
"/api/search": ["./docs/**/*", "./blog/**/*", "./README.md"],
},
},
};
import type { NextConfig } from "next";

const nextConfig = {
outputFileTracingIncludes: {
"/api/search": ["./docs/**/*", "./blog/**/*", "./README.md"],
},
};
/** @type {import("next").NextConfig} */
const nextConfig: NextConfig = {
outputFileTracingIncludes: {
"/api/search": ["./docs/**/*", "./blog/**/*", "./README.md"],
},
};

For more details on search configuration, refer to the Search page.

Sitemap Setup 

To generate a sitemap in next.js, you can use a special sitemap file  in combination with getStaticParams tool:

TypeScript
JavaScript
import { type MetadataRoute } from "next";
import { getStaticParams } from "./docs/robindoc";

const sitemap = async (): Promise<MetadataRoute.Sitemap> => {
const staticParams = await getStaticParams();

return staticParams.map(({ segments }) => ({
url: `https://robindoc.com${segments.join("/")}`,
lastModified: new Date(),
changeFrequency: "daily",
priority: 0.7,
}));
};

export default sitemap;
import { type MetadataRoute } from "next";
import { getStaticParams } from "./docs/robindoc";

const sitemap = async () => {
const staticParams = await getStaticParams();

return staticParams.map(({ segments }) => ({
url: `https://robindoc.com/${segments.join('/')}`,
lastModified: new Date(),
changeFrequency: "daily",
priority: 0.7,
}));
};

export default sitemap;
Previous
Initialization
Next
Structure
Return to navigation