Vybex
Security Setup
One-click security config for your stack. Copy, paste, done.
Framework
Deploy To
Security Headers
Protects against XSS, clickjacking, MIME sniffing, and more.
Protects: XSS, Clickjacking, MIME Sniffing, Referrer Leaks
next.config.ts
const nextConfig = {
async headers() {
return [{
source: '/(.*)',
headers: [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'X-XSS-Protection', value: '1; mode=block' },
{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
{ key: 'Content-Security-Policy', value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" },
],
}];
},
productionBrowserSourceMaps: false,
};
export default nextConfig;CORS Configuration
Only allow requests from YOUR domain. Never use wildcard '*' in production.
Protects: Cross-Origin Attacks, Data Theft
src/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const allowedOrigins = [
'https://yourdomain.com',
process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : '',
].filter(Boolean);
export function middleware(request: NextRequest) {
const origin = request.headers.get('origin') ?? '';
const res = NextResponse.next();
if (allowedOrigins.includes(origin)) {
res.headers.set('Access-Control-Allow-Origin', origin);
res.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.headers.set('Access-Control-Allow-Credentials', 'true');
}
return res;
}Rate Limiting
Prevents brute-force attacks and API abuse. Essential for login endpoints.
Protects: Brute Force, DDoS, API Abuse
src/lib/rate-limit.ts
import { NextResponse } from 'next/server';
const rateLimit = new Map<string, { count: number; resetTime: number }>();
export function rateLimitCheck(ip: string, limit = 10, windowMs = 60000) {
const now = Date.now();
const record = rateLimit.get(ip);
if (!record || now > record.resetTime) {
rateLimit.set(ip, { count: 1, resetTime: now + windowMs });
return { allowed: true, remaining: limit - 1 };
}
if (record.count >= limit) {
return { allowed: false, remaining: 0 };
}
record.count++;
return { allowed: true, remaining: limit - record.count };
}
// Usage in API route:
// const { allowed } = rateLimitCheck(request.ip ?? '127.0.0.1', 5, 900000);
// if (!allowed) return NextResponse.json({ error: 'Too many requests' }, { status: 429 });Environment Variable Validation
Crash early if required env vars are missing. Never run with missing secrets.
Protects: Misconfiguration, Secret Exposure
src/lib/env.ts
const requiredEnvVars = [
'DATABASE_URL',
'JWT_SECRET',
'NEXT_PUBLIC_APP_URL',
] as const;
type EnvVars = Record<(typeof requiredEnvVars)[number], string>;
function validateEnv(): EnvVars {
const missing: string[] = [];
for (const key of requiredEnvVars) {
if (!process.env[key]) {
missing.push(key);
}
}
if (missing.length > 0) {
throw new Error(
`Missing required environment variables:\n${missing.map(k => ` - ${k}`).join('\n')}`
);
}
return Object.fromEntries(
requiredEnvVars.map(key => [key, process.env[key]!])
) as EnvVars;
}
export const env = validateEnv();Security .gitignore
Prevents accidentally committing secrets, keys, and sensitive files.
Protects: Secret Exposure, Source Code Leak
.gitignore
# Dependencies node_modules/ # Environment .env .env.* !.env.example # Secrets & Keys *.pem *.key *.cert *.p12 # Build .next/ dist/ build/ .vercel/ # IDE .idea/ .vscode/settings.json # OS .DS_Store Thumbs.db # Debug *.log npm-debug.log* # Database *.sql *.dump *.sqlite