Build a Privacy-First Self-Hosted AI Accessibility Compliance Checker for Indian Public-Facing Web Apps (GIGW 3.0 & WCAG 2.2) with Next.js, Ollama, and pgvector: 2026 Step-by-Step Guide for Developers
Learn to build a fully self-hosted, privacy-first AI accessibility compliance checker aligned with India's GIGW 3.0 and global WCAG 2.2 standards, using Next.js, Ollama, and pgvector—no third-party data sharing, no recurring SaaS costs, and full control for public sector teams in 2026.
Agentic Academy Labs
Author
Introduction
In 2026, India’s GIGW 3.0 (Government of India Guidelines for Web and Mobile Applications) and global WCAG 2.2 accessibility standards are mandatory for all public-facing web apps handling citizen data—from Aadhaar enrollment portals to state health dashboards. Most off-the-shelf accessibility checkers are SaaS tools that upload sensitive user page data to third-party servers, violating the country’s DPDP Act 2023 and creating unnecessary risk for public sector teams.
This step-by-step guide walks you through building a fully self-hosted, privacy-first AI accessibility compliance checker that runs entirely on your own infrastructure, with zero data exfiltration. We’ll use Next.js 14, Ollama for local LLM inference, and pgvector for fast semantic rule matching, with out-of-the-box support for GIGW 3.0 and WCAG 2.2 requirements.
Why Privacy-First Accessibility Tools Are Non-Negotiable for Indian Public Apps
Public-facing government apps process highly sensitive citizen data: tax records, health information, identity verification details, and more. Sending this data to third-party SaaS accessibility tools creates compliance risk under the DPDP Act, which mandates that personal data stay within controlled, auditable infrastructure.
Beyond compliance, self-hosted tools offer unique benefits for public sector teams:
- No recurring SaaS costs: Pay only for your own server infrastructure, no per-scan fees
- Custom rule support: Add GIGW 3.0-specific requirements (like regional language accessibility rules) that generic global checkers miss
- Full audit control: All scan logs and results stay on your servers for official compliance documentation
- Air-gap compatibility: Run the entire stack in isolated, offline environments for highly sensitive use cases
Tech Stack Overview
We’ve chosen tools that are widely adopted in Indian government and enterprise stacks, with built-in privacy guarantees:
- Next.js 14 (App Router): For a fast, accessible frontend with React Server Components to reduce client-side data exposure
- Ollama: Runs open-source LLMs (we’ll use a Llama 3 8B model fine-tuned on accessibility rules) locally on your server, with no external API calls
- pgvector: A PostgreSQL extension for vector storage, used to index GIGW 3.0 and WCAG 2.2 rules for fast semantic matching
- Drizzle ORM: Type-safe database access with built-in pgvector support
- Tailwind CSS: For building an accessible, WCAG 2.2 AA-compliant UI out of the box
Step 1: Set Up Your Project Foundation
First, ensure you have the following prerequisites installed:
- Node.js 20+
- PostgreSQL 16+ with the pgvector extension enabled
- Ollama (for local LLM and embedding inference)
Create your Next.js project and install dependencies:
npx create-next-app@latest accessibility-checker --typescript --tailwind --app --no-src-dir --import-alias "@/*"
cd accessibility-checker
npm install pg @ollama/ai pgvector drizzle-orm drizzle-kit
Enable pgvector in your PostgreSQL instance, then create a .env file with your database connection string:
DATABASE_URL=postgres://user:password@localhost:5432/accessibility_checker
OLLAMA_HOST=http://localhost:11434
Step 2: Configure Ollama for Local Accessibility Analysis
First, pull a fine-tuned Llama 3 8B model trained on GIGW 3.0 and WCAG 2.2 rule sets, plus a local embedding model for semantic search:
ollama pull llama3:8b-instruct-accessibility
ollama pull nomic-embed-text
Next, build a Next.js API route to call Ollama locally, with no external API keys required. This route will receive page content and relevant accessibility rules, then return a list of compliance issues:
// app/api/analyze/route.ts
import { Ollama } from '@ollama/ai';
import { db } from '@/lib/db';
import { rules } from '@/lib/schema';
const ollama = new Ollama({ host: process.env.OLLAMA_HOST });
export async function POST(request: Request) {
const { pageContent, url } = await request.json();
// Generate embedding for the page content to find relevant rules
const embeddingResponse = await ollama.embed({
model: 'nomic-embed-text',
prompt: pageContent,
});
const pageEmbedding = embeddingResponse.embeddings[0];
// Find top 10 most relevant accessibility rules via pgvector similarity search
const relevantRules = await db.query.rules.findMany({
orderBy: (rules, { sql }) => [sql`${rules.embedding} <=> ${pageEmbedding}::vector`],
limit: 10,
});
// Send rules and page content to local LLM for analysis
const analysisResponse = await ollama.chat({
model: 'llama3:8b-instruct-accessibility',
messages: [
{
role: 'system',
content: "You are an accessibility compliance expert for Indian public-facing web apps. Analyze the provided page content against the given GIGW 3.0 and WCAG 2.2 rules. Return a JSON array of issues, each with: severity (critical/warning/info), ruleName, ruleSource (GIGW 3.0 or WCAG 2.2), description, and suggestedFix. Only return valid JSON, no extra text.",
},
{
role: 'user',
content: `Page URL: ${url}\nPage Content: ${pageContent}\nRelevant Rules: ${JSON.stringify(relevantRules.map(r => r.ruleText))}`,
},
],
format: 'json',
});
return Response.json(JSON.parse(analysisResponse.message.content));
}
All processing happens on your local server—no page content or user data is ever sent to external providers.
Step 3: Set Up pgvector for Semantic Rule Matching
Instead of hardcoding hundreds of accessibility rules, we store embeddings of GIGW 3.0 and WCAG 2.2 rules in pgvector for fast, context-aware matching. First, define your database schema with Drizzle:
// lib/schema.ts
import { pgTable, text, vector, varchar } from 'drizzle-orm/pg-core';
import { sql } from 'drizzle-orm';
export const rules = pgTable('accessibility_rules', {
id: varchar('id').primaryKey().default(sql`gen_random_uuid()`),
ruleText: text('rule_text').notNull(),
embedding: vector('embedding', { dimensions: 1536 }).notNull(),
source: varchar('source', { enum: ['GIGW 3.0', 'WCAG 2.2'] }).notNull(),
severity: varchar('severity', { enum: ['critical', 'warning', 'info'] }).notNull(),
});
Next, seed your database with the full set of GIGW 3.0 and WCAG 2.2 rules. Use the local nomic-embed-text model to generate embeddings, so no external API calls are needed:
// lib/seed-rules.ts
import { db } from './db';
import { rules } from './schema';
import { Ollama } from '@ollama/ai';
const ollama = new Ollama({ host: process.env.OLLAMA_HOST });
const gigwRules = [
"All public-facing pages must have skip navigation links for keyboard users",
"All form fields must have associated ARIA labels in English and the relevant regional language",
"Color contrast between text and background must meet a minimum 4.5:1 ratio for normal text",
// Add full GIGW 3.0 rule set here
];
const wcagRules = [
"All non-decorative images must have descriptive alt text",
"All interactive elements must be keyboard accessible",
"Pages must have a logical heading hierarchy (h1-h6)",
// Add full WCAG 2.2 rule set here
];
async function seed() {
for (const rule of [...gigwRules, ...wcagRules]) {
const embeddingResponse = await ollama.embed({
model: 'nomic-embed-text',
prompt: rule,
});
await db.insert(rules).values({
ruleText: rule,
embedding: embeddingResponse.embeddings[0],
source: rule.startsWith('All public') ? 'GIGW 3.0' : 'WCAG 2.2',
severity: rule.includes('must') ? 'critical' : 'warning',
});
}
console.log('Rules seeded successfully');
}
seed();
Step 4: Build an Accessible Frontend
Your checker’s UI must itself be WCAG 2.2 AA compliant to set a standard for the apps it evaluates. Use Next.js App Router with React Server Components for fast initial loads, and a client component for the URL input form:
// app/page.tsx
import UrlInput from './url-input';
import ResultsDisplay from './results-display';
export default function Home() {
return (
<main className="min-h-screen bg-gray-50 p-8">
<div className="max-w-4xl mx-auto">
<h1 className="text-3xl font-bold text-gray-900 mb-2">
GIGW 3.0 & WCAG 2.2 Accessibility Compliance Checker
</h1>
<p className="text-gray-600 mb-8">
Self-hosted, privacy-first compliance analysis for Indian public web apps
</p>
<UrlInput />
<ResultsDisplay />
</div>
</main>
);
}
The UrlInput component uses proper ARIA labels, keyboard navigation, and error messaging to meet accessibility standards:
// app/url-input.tsx
'use client';
import { useState } from 'react';
export default function UrlInput() {
const [url, setUrl] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
// Add fetch call to your /api/analyze route here
setLoading(false);
};
return (
<form onSubmit={handleSubmit} className="mb-8">
<label htmlFor="url-input" className="block text-sm font-medium text-gray-700 mb-2">
Enter public web app URL to scan
</label>
<div className="flex gap-4">
<input
id="url-input"
type="url"
value={url}
onChange={(e) => setUrl(e.target.value)}
required
className="flex-1 px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
aria-describedby="url-error"
/>
<button
type="submit"
disabled={loading}
className="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:bg-blue-300"
>
{loading ? 'Scanning...' : 'Run Compliance Check'}
</button>
</div>
{error && (
<p id="url-error" className="mt-2 text-sm text-red-600" role="alert">
{error}
</p>
)}
</form>
);
}
The results display groups issues by severity, links to the full GIGW 3.0 or WCAG 2.2 rule, and includes suggested fixes for developers.
Step 5: Harden Privacy and Security
To meet public sector security requirements, add these hardening steps:
- Isolate Ollama: Run Ollama in a Docker container with no internet access for air-gapped deployments
- Encrypt data at rest: Enable PostgreSQL encryption for stored scan logs and rule sets
- Authenticate API routes: Add OAuth2 or SSO authentication for authorized compliance teams only
- Sanitize inputs: Validate and sanitize all user-submitted URLs to prevent SSRF and XSS attacks
- Rate limiting: Add rate limiting to API routes to prevent abuse
- No telemetry: Disable all Next.js and Ollama telemetry to ensure no usage data leaves your server
Step 6: Test with Real Indian Public App Use Cases
Test your checker with common public sector page types to validate GIGW 3.0 support:
- Aadhaar enrollment forms: Check for missing regional language labels and ARIA support
- State government service portals: Validate color contrast for regional language text and skip navigation links
- Public health dashboards: Check alt text for regional language infographics and data table accessibility
The local LLM will flag GIGW-specific issues that generic global checkers miss, like missing support for Devanagari script contrast ratios or regional language form labels.
Deployment for 2026 Public Sector Use
Deploy the entire stack via Docker Compose for consistent, reproducible deployments across government on-prem servers or India’s MeghRaj cloud:
# docker-compose.yml
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:password@db:5432/accessibility_checker
- OLLAMA_HOST=http://ollama:11434
depends_on:
- db
- ollama
db:
image: pgvector/pgvector:pg16
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=accessibility_checker
volumes:
- pgdata:/var/lib/postgresql/data
ollama:
image: ollama/ollama:latest
volumes:
- ollama-models:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu] # Optional, for faster LLM inference
volumes:
pgdata:
ollama-models:
A standard 8-core, 32GB RAM server can run the entire stack for thousands of daily scans, at a fraction of the cost of enterprise SaaS accessibility tools.
Conclusion
This self-hosted, privacy-first accessibility checker gives Indian public sector teams full control over their compliance workflow, with built-in support for GIGW 3.0 and WCAG 2.2, zero third-party data sharing, and no recurring costs. The entire codebase is open source, and Agentic Academy Labs offers a full hands-on course with pre-configured lab environments to help you build and deploy the tool for your team in 2026.
Enjoyed this article?
Read More Articles