Server-Side Rendering & RSC
Use the NDPA Toolkit on the server: /server subpath, validateDsrSubmission, and React Server Components.
Overview
The NDPA Toolkit ships two distinct surfaces:
- A client surface — the React components, hooks, and providers exported from
@tantainnovative/ndpr-toolkit. These carry"use client"directives and depend on React. - A pure-logic surface — validators, generators, scoring, locales, and storage adapters exported from
@tantainnovative/ndpr-toolkit/server. Zero React in the import graph.
Use /server for any server-side context: Next.js Route Handlers and Server Components, NestJS controllers, Express middleware, Cloudflare Workers, Edge Functions, or scheduled background jobs. Build-output guard tests assert this entry never carries a "use client" directive and never imports react or react-dom — the RSC-safety contract is structurally enforced.
What's in /server
All 50 exports are pure functions or types — no React, no DOM dependency.
| Category | Exports |
|---|---|
| Validators | validateConsent, validateDsrSubmission, validateProcessingActivity, validateTransfer, validateProcessingRecord |
| Generators & export | generatePolicyText, assemblePolicy, findUnfilledTokens, exportHTML, exportMarkdown, exportPDF, exportDOCX |
| Domain helpers | formatDSRRequest, assessDPIARisk, calculateBreachSeverity, getComplianceScore, sanitizeInput |
| Locales | defaultLocale (en), yorubaLocale, igboLocale, hausaLocale, pidginLocale, mergeLocale |
| Storage adapters | apiAdapter, memoryAdapter, localStorageAdapter, sessionStorageAdapter, cookieAdapter, composeAdapters |
| Types | Every type from the public API plus DsrSubmissionPayload, DsrSubmissionValidationResult |
Validating DSR submissions on the server
The most common server-side use case: receiving a payload from <DSRRequestForm onSubmit> and validating it before persisting. validateDsrSubmission mirrors the rules the client form enforces, so client and server stay in sync without you hand-rolling zod or class-validator schemas.
Next.js App Router
// app/api/dsr/route.ts
import { validateDsrSubmission } from '@tantainnovative/ndpr-toolkit/server';
import { dsrStore } from '@/lib/dsr-store';
export async function POST(req: Request) {
const result = validateDsrSubmission(await req.json(), {
allowedRequestTypes: ['access', 'erasure', 'rectification', 'objection'],
});
if (!result.valid) {
return Response.json({ errors: result.errors }, { status: 422 });
}
// result.data is the typed DsrSubmissionPayload — narrowed and safe to persist.
await dsrStore.create({
...result.data,
receivedAt: Date.now(),
status: 'pending',
});
return Response.json({ ok: true }, { status: 201 });
}NestJS controller
// dsr.controller.ts
import { Body, Controller, Post, HttpException, HttpStatus } from '@nestjs/common';
import { validateDsrSubmission } from '@tantainnovative/ndpr-toolkit/server';
@Controller('dsr')
export class DsrController {
constructor(private readonly dsrService: DsrService) {}
@Post()
async submit(@Body() body: unknown) {
const result = validateDsrSubmission(body);
if (!result.valid) {
throw new HttpException(
{ errors: result.errors },
HttpStatus.UNPROCESSABLE_ENTITY,
);
}
return this.dsrService.create(result.data);
}
}Express middleware
// routes/dsr.ts
import { Router } from 'express';
import { validateDsrSubmission } from '@tantainnovative/ndpr-toolkit/server';
export const dsrRouter: Router = Router();
dsrRouter.post('/dsr', async (req, res) => {
const result = validateDsrSubmission(req.body);
if (!result.valid) {
return res.status(422).json({ errors: result.errors });
}
await dsrStore.create(result.data);
res.status(201).json({ ok: true });
});Skip the identity-verification check (e.g. when the consumer's session already authenticated the user) by passing { requireIdentityVerification: false } as the second argument.
Generating policies on the server
Render a privacy policy server-side (for SSR, scheduled email reports, or PDF generation) without loading any React:
// app/api/privacy-policy/pdf/route.ts
import {
assemblePolicy,
exportPDF,
createDefaultContext,
findUnfilledTokens,
} from '@tantainnovative/ndpr-toolkit/server';
export async function GET() {
const ctx = createDefaultContext();
ctx.org.name = 'Acme Nigeria Ltd';
ctx.org.privacyEmail = 'privacy@acme.ng';
ctx.org.website = 'https://acme.ng';
const sections = assemblePolicy(ctx);
const policy = {
id: 'p',
title: 'Acme Privacy Policy',
templateId: 'default-business',
organizationInfo: ctx.org,
sections,
variableValues: { orgName: ctx.org.name, /* ... */ },
effectiveDate: Date.now(),
lastUpdated: Date.now(),
version: '1.0',
};
// CI-style guard before sending to the wire.
const missing = findUnfilledTokens(JSON.stringify(sections));
if (missing.length) {
throw new Error(`Policy is missing: ${missing.join(', ')}`);
}
const blob = await exportPDF(policy);
return new Response(blob, {
headers: { 'Content-Type': 'application/pdf' },
});
}RSC imports — picking the right entry
| From | Import | RSC-safe |
|---|---|---|
| Server Component / Route Handler | /server | ✅ |
| Client Component (form, banner, etc.) | root @tantainnovative/ndpr-toolkit or feature subpath | N/A |
| Storage adapters in any context | /adapters | ✅ |
| Types (only) | /server (preferred) or feature subpath as type imports | ✅ when typed |
The /core entry exists for back-compat — it includes the React NDPRProvider alongside the pure logic. Prefer /server for new server-side code.
See also
- Backend Integration — full database wiring with Prisma and Drizzle.
- Storage Adapters — composing storage backends with
apiAdapterand friends. - Upgrading from 3.3.x — when
/serverarrived and what it replaced.