API
Built-in API routes: search, GitHub releases and version, on a server deployment and in a static export
The template ships three read-only GET routes, all in the app/api folder:
Each one works in both deploy modes, but answers differently in a static export (see Static export below).
Search API
The full-text search is powered by Orama through
fumadocs-core/search/server: the route builds the index from the docs content source
itself, with no separate indexing step and no extra build command.
app/api/search/route.ts
const server = createFromSource(source, {
language: config.search.language,
});
export const GET = process.env.NEXT_PUBLIC_STATIC_EXPORT === '1' ? server.staticGET : server.GET;On a server deployment
The index stays on the server and the route answers queries: try /api/search?query=mantine. It returns an array of entries, one for each page, heading or paragraph that matches:
[
{
"id": "/docs/mantine",
"type": "page",
"content": "Mantine",
"breadcrumbs": ["Documentation", "Examples"],
"url": "/docs/mantine"
},
{
"id": "/docs/mantine-6",
"type": "text",
"content": "Mantine Marquee",
"url": "/docs/mantine#third-party-components"
}
]type is page, heading or text, and url links straight to the page or the section. Without a query, the route returns an empty array.
Client side
The search UI is a Mantine Spotlight fed by the
headless useDocsSearch hook: open it with ⌘K (or /). It queries the route on a server
deployment, and downloads the index in a static export:
components/docs/DocsSearch.tsx
const { search, setSearch, query } = useDocsSearch(
isStaticExport ? { type: 'static', from: `${basePath}/api/search` } : { type: 'fetch' }
);Config
The search language and the filter row above the results (All, Guides, Examples,
Reference) live in the config/index.ts file:
config/index.ts
/**
* Search configuration (Orama via fumadocs-core)
* @see /app/api/search/route.ts
*/
search: {
language: 'english',
filters: [
{ label: 'All', prefixes: [] as string[] },
{ label: 'Guides', prefixes: ['/docs/guides'] },
{ label: 'Examples', prefixes: ['/docs/mantine', '/docs/inline-svg'] },
{ label: 'Reference', prefixes: ['/docs/api', '/docs/versioning'] },
],
},GitHub Release API
On a server deployment, /api/github-releases proxies the GitHub REST API and returns the
latest releases of the repository set in config/index.ts: as many as
releaseNotes.maxReleases, 10 by default. The Release Notes page is
built on it.
Response
{
"releases": [],
"status": "ok"
}releases holds GitHub's own release objects, newest first (tag_name, name, body,
published_at, html_url and the rest), exactly as GitHub's
List releases endpoint
returns them.
Refused requests and errors
Caching
The call to GitHub is cached for an hour (next: { revalidate: 3600 }), and the route
answers with Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400: a CDN
such as Vercel's serves the same response for an hour, and a stale one for up to a day
while it refreshes.
Config
config/index.ts
/**
* GitHub API configuration
* @see https://docs.github.com/en/rest/reference/repos#releases
*
* The GitHub API token is optional for rate limiting.
* If you want to use it, create a read-only token: a fine-grained token with
* public repositories (read-only), or a classic token with no scopes.
*
* This information is used to fetch the releases from the GitHub API.
*/
gitHub: {
repo: 'gfazioli/next-app-fumadocs-template',
apiUrl: 'https://api.github.com',
releasesUrl: 'https://api.github.com/repos/gfazioli/next-app-fumadocs-template/releases',
},
/**
* Release notes configuration
* This is used to link the release notes in the app.
*/
releaseNotes: {
url: 'https://github.com/gfazioli/next-app-fumadocs-template/releases',
maxReleases: 10,
},repo is also sent to GitHub as the request's User-Agent, which GitHub requires.
Rate limiting and GITHUB_TOKEN
Without a token, GitHub allows 60 requests per hour from each IP address; with a token,
5,000 per hour. To use one, set the GITHUB_TOKEN environment variable: the route reads it
on its own, with no code to change. If GitHub rejects the token (a 401, or a 403 that is
not a rate limit), the route retries without it, since public releases can be read
anonymously.
A read-only token is enough, as the config comment above says: the repo scope is not
needed, and it would grant full access to your private repositories.
Version API
/api/version returns the version field of package.json, for example:
{ "version": "1.0.0" }Static export
yarn build:static builds a fully static site (output: 'export'), the way this
template's own demo is deployed on GitHub Pages. It sets NEXT_PUBLIC_STATIC_EXPORT=1,
and NEXT_PUBLIC_BASE_PATH sets the base path (/next-app-fumadocs-template on Pages).
Nothing has to be changed by hand: the routes and the components read the same variable.
With no server to answer requests, each route is written out once at build time:
/api/searchbecomes the whole Orama index, exported as a static file. The search box downloads it and runs every query in the browser, so the route ignoresquerythere./api/github-releasesbecomes a stub,{ "error": "Not available in static export", "releases": [] }. The Release Notes page calls the GitHub API straight from the browser instead, within GitHub's 60 requests per hour for each visitor's IP address./api/versionbecomes the version of the build.