Skip to content

Latest commit

 

History

History
382 lines (282 loc) · 12.4 KB

04-app-organization.md

File metadata and controls

382 lines (282 loc) · 12.4 KB

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.

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):

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.

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.

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.

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 });
};
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.

/** @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:

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;