Isolated technical documentation portal in a next.js SaaS: nextra, prisma, and auth middleware without database pollution
Back to blog

Isolated technical documentation portal in a next.js SaaS: nextra, prisma, and auth middleware without database pollution

6/7/2026 · 5 min · Development

Architectural problem: fragmented technical documentation inside an active SaaS repository#

In mature production stacks, technical documentation tends to evolve in parallel with the product - isolated .md files accumulate in the repository without navigation structure, search capability, or access control. This was the exact scenario encountered: a SaaS built on Next.js + React + Node + Prisma + MongoDB with dozens of critical modules documented in loose Markdown - complete Prisma schemas, API payloads, ASCII art architecture diagrams, and state transition tables - but with no unified portal layer for the team to consume.

The operational question that needed answering before any implementation: how to consolidate this documentation into a professional portal without introducing dependencies that pollute the database, break the CI/CD pipeline, or publicly expose sensitive system internals?


1. Prerequisites and minimum required versions#

To ensure stability and prevent package bundling issues during the final application build, the following minimum version requirements are established:

Technical DependencyMinimum Required VersionPortal Role
Next.js14.0+ (App Router or Pages Router)Application core framework
Nextra3.0+MDX documentation rendering engine
Node.js18.0+ (LTS)Server execution runtime
Clerk / NextAuth.jsLatest stable configuredAuth and session provider
Prisma ORM5.0+Data mapping layer (unaffected by Nextra)
MongoDB6.0+Transactional database (unaffected by Nextra)

2. Comparative tool audit#

Before committing any changes to the repository, a structural analysis of documentation portal alternatives was conducted:

Docusaurus (meta)#

The industry standard for developer-focused portals. Native MDX support, API versioning, i18n, and Algolia search bar out of the box. Architectural blocker: requires a completely separate React/Node project. This implies a second CI/CD pipeline, second build process, second environment variable configuration, and a maintenance overhead that did not justify the objective - internal documentation for a small team on an already consolidated stack.

Domain.com / mintlify and similar#

SaaS-based external tools are designed for public content extraction or manuals for end customers. Architectural blocker: hosting technical infrastructure documentation and internal data schemas on third-party clouds exposes code to unnecessary security risks, in addition to requiring constant syncing of keys and access control via external OAuth.

Nextra#

Documentation framework built on top of pure Next.js. The value proposition is direct: zero additional infrastructure overhead since it operates within the same existing next build process. Supports MDX natively, renders Markdown tables with full fidelity, preserves monospace spacing in ASCII diagrams, and applies automatic syntax highlighting for TypeScript, Shell, and Prisma code blocks. Zero new infrastructure dependency.


3. Forensic validation: does nextra touch mongodb/prisma?#

This was the critical validation question before implementation. The concern was legitimate: a module installed as a Next.js dependency could attempt to import the Prisma Client or open database connections during the server lifecycle.

Analysis result: Nextra operates exclusively at the filesystem layer during the Next.js build process. Its transformation pipeline is as follows:

/docs/*.md (filesystem)
   ↓
  Nextra Parser (remark + rehype plugins)
   ↓
  Next.js getStaticProps / getStaticPaths
   ↓
  Static HTML generated at build time
   ↓
  Served via CDN / Next.js Static File Handler

Nextra does not:

It is strictly a Markdown → HTML transformer executed by the Next.js SSG engine (getStaticProps), operating in complete isolation from the ORM layer.


4. Detailed theme configuration (theme.config.jsx)#

Visual behavior, integrated search indexation, and UI preferences are managed centrally in the theme.config.jsx file at the root of the project.

// theme.config.jsx
import React from 'react'

export default {
  logo: <span>📘 Internal DocCenter</span>,
  project: {
    link: 'https://github.com/your-org/your-repo',
  },
  docsRepositoryBase: 'https://github.com/your-org/your-repo/tree/main/docs',
  head: (
    <>
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <meta name="description" content="Internal Technical Documentation Portal - SaaS" />
      <meta name="robots" content="noindex, nofollow" /> {/* Shield external SEO */}
    </>
  ),
  
  // Nextra includes built-in full-text search based on FlexSearch
  search: {
    placeholder: 'Search documentation...',
  },

  // Dark Mode and Design System settings
  darkMode: true, // Enable dark/light theme toggle
  nextThemes: {
    defaultTheme: 'dark', // Force dark theme by default
  },
  primaryHue: {
    dark: 210, // Blue tones for dark theme
    light: 200
  },
  primarySaturation: 100,

  footer: {
    text: () => (
      <span>
        © {new Date().getFullYear()} Internal DocCenter. Access restricted to authorized engineers.
      </span>
    )
  }
}

5. MDX and advanced nextra components#

With Nextra, you can render dynamic React components directly in the body of Markdown documentation. Below are examples of using the integrated utility components Callout and Steps.

Example of usage in MDX (badges.md)#

---
title: Badge System
description: Technical documentation for the gamification and achievement system.
---

import { Callout, Steps } from 'nextra/components'

# Badge System - Technical Documentation

## Data model (prisma schema)

model Badge { id String @id @default(auto()) @map("_id") @db.ObjectId name String @unique slug String @unique category BadgeCategory createdAt DateTime @default(now()) }


<Callout type="warning" emoji="⚠️">
  **Migration Warning**: Modifications to the `Badge` model require executing the bulk data sync script `npx prisma db push`. Make sure no active transactions remain pending.
</Callout>

## Webhook integration flow

<Steps>
### Configure webhook endpoint
Configure the receiving listener at URL `/api/webhooks/canny`.
 
### Validate canny signature
Verify the `X-Canny-Signature` header using the shared cryptographic secret.
 
### Process grant
If the event is `post.status_changed` and moves to `completed`, execute the Badge allocation routine in the database.
</Steps>

6. Security engineering: protecting the /docs route via middleware#

The documentation contains Prisma schemas with data types, webhook payloads with internal event structures, and detailed authentication flows. Public exposure is unacceptable. Protection was implemented by leveraging the existing Next.js Middleware already present in the application.

middleware.ts configuration with session handling#

The authentication middleware (using Clerk or NextAuth.js) intercepts calls on routes specified in the matcher and evaluates the session cookie lifetime.

// middleware.ts
import { authMiddleware } from "@clerk/nextjs"; // or equivalent NextAuth

export default authMiddleware({
  // Explicitly public routes - no active session required
  publicRoutes: [
    "/",
    "/login",
    "/signup",
    "/api/public",
    "/api/webhooks/(.*)"
  ],
  // Restricted routes and redirect flow
  // If the session expires (Session Timeout), it automatically redirects the user to the login page
});

export const config = {
  matcher: [
    // Capture all page and API routes, except static files
    "/((?!.*\\..*|_next).*)",
    "/",
    "/(api|trpc)(.*)",
    "/docs/:path*" // Documentation route explicitly captured
  ],
};

Authentication CLI test roadmap#

To validate the security of the /docs/badges route without using browsers, run the following requests with curl:

# 1. Test anonymous access (No authentication)
# Expected: Return HTTP 302 with Location header pointing to login
curl -I http://localhost:3000/docs/badges

# 2. Test access with Invalid or Expired Session Token
# Expected: Return HTTP 401 Unauthorized or security redirection
curl -I -H "Authorization: Bearer <INVALID_TOKEN>" http://localhost:3000/docs/badges

# 3. Test access with Valid Authenticated Token
# Expected: Return HTTP 200 OK with structured documentation content
curl -I -H "Authorization: Bearer <VALID_TOKEN>" http://localhost:3000/docs/badges

7. Versioning documentation via subdirectories#

Nextra supports static documentation versioning by organizing .md files in dedicated subfolders and configuring specific _meta.json files for each version:

/docs
  /v1
    _meta.json
    index.md
    badges.md
  /v2
    _meta.json
    index.md
    badges.md

Configuring _meta.json in the /docs/v2/ directory#

{
  "index": "Overview V2",
  "badges": "🏅 Advanced Badge System (V2)"
}

8. Production build validation#

To validate that the Next.js compiler successfully generated the static pages and mapped all Nextra paths without syntax errors:

# 1. Execute production static build
npm run build

# 2. Inspect logs for compilation warnings or errors
npm run build 2>&1 | grep -i "error"

# 3. Validate if static files were physically generated in the output directory
ls -la .next/server/app/docs/

# 4. Check the total size of frontend static chunks
ls -la .next/static/chunks/

# 5. Start the server in production mode
npm run start

Fire a local request against the generated route to validate the response header:

# Test if the documentation index page responds successfully after build
curl -I http://localhost:3000/docs/

9. Documentation deployment strategies#

Native deploy via vercel#

If your Next.js stack already uses Vercel, the deploy is automatically integrated into each push on the protected branch. To trigger manually in production mode:

# Execute production build and deploy on Vercel
vercel --prod

Containerized deploy via multi-stage Docker#

If your SaaS runs on clouds like AWS, GCP, or dedicated infrastructures, use the following optimized Dockerfile:

# Stage 1: Install and Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production Runner
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json

EXPOSE 3000
USER node
CMD ["npm", "run", "start"]

Docker compose configuration (docker-compose.yml)#

version: "3.8"
services:
  saas-app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    restart: always
    environment:
      - NODE_ENV=production

Checklist: nextra documentation in next.js SaaS#

1. Pre-implementation#

2. Build and layout settings#

3. Content migration and versioning#

4. Security and middleware#

5. Compiling, validation, and deploy#

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments