This guide documents the redirect management system implemented for TeachLink, specifically for handling Privacy Policy page redirects and general URL redirects across the application.
┌─────────────────────────────────────┐
│ Next.js Middleware │
│ (src/middleware.ts) │
│ ├─ Handle Redirects (early) │
│ ├─ RBAC Checks │
│ ├─ Security Headers │
│ └─ CSP Headers │
└────────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Redirect Middleware │
│ (src/middleware/redirectManagement.ts)
│ ├─ Pattern Matching │
│ ├─ Query Parameter Preservation │
│ ├─ Locale Handling │
│ └─ Analytics Logging │
└────────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Redirect Rules Engine │
│ (src/lib/redirectManagement.ts) │
│ ├─ Rule Configuration │
│ ├─ Pattern Matching │
│ ├─ Destination Building │
│ └─ Redirect Logging │
└─────────────────────────────────────┘
src/
├── lib/
│ └── redirectManagement.ts # Core redirect logic
│ └── __tests__/
│ └── redirectManagement.test.ts
│
├── middleware/
│ ├── redirectManagement.ts # Middleware integration
│ │ └── __tests__/
│ │ └── redirectManagement.test.ts
│ └── (other middleware files)
│
├── app/
│ ├── privacy/
│ │ ├── page.tsx # Privacy Policy page
│ │ └── __tests__/
│ │ └── privacy-page.test.tsx
│ │
│ └── middleware.ts # Main middleware
│
└── components/
└── legal/
└── PrivacyPolicyContent.tsx # Privacy content component
The system includes the following legacy privacy policy URLs that redirect to /privacy:
| From | To | Status | Query Params | Hash |
|---|---|---|---|---|
/privacy-policy |
/privacy |
308 | ✓ | ✓ |
/privacy-notice |
/privacy |
308 | ✓ | ✗ |
/policies/privacy |
/privacy |
308 | ✓ | ✗ |
/legal/privacy |
/privacy |
308 | ✓ | ✗ |
/legal/privacy-policy |
/privacy |
308 | ✓ | ✗ |
| From | To | Status | Query Params | Hash |
|---|---|---|---|---|
/terms-of-service |
/terms |
308 | ✓ | ✗ |
/tos |
/terms |
308 | ✓ | ✗ |
- 308 (Permanent Redirect): Default for most redirects. Preserves HTTP method (POST stays POST).
- 301 (Moved Permanently): For permanent moves. Changes POST to GET.
- 302 (Found): For temporary redirects. Changes POST to GET.
To add a new redirect rule, modify src/lib/redirectManagement.ts:
// For Privacy Policy related redirects
export const PRIVACY_POLICY_REDIRECTS: RedirectRule[] = [
{
from: '/new-old-url',
to: '/privacy',
status: 308,
preserveQuery: true,
preserveHash: false,
isLegacy: true,
},
// ... existing rules
];
// For global redirects
export const GLOBAL_REDIRECTS: RedirectRule[] = [
{
from: '/some-old-page',
to: '/some-new-page',
status: 308,
preserveQuery: true,
},
// ... existing rules
];interface RedirectRule {
// Source URL pattern (supports wildcards)
from: string;
// Destination URL
to: string;
// HTTP status code (default: 308)
status?: number;
// Preserve query parameters (default: true)
preserveQuery?: boolean;
// Preserve hash fragment (default: false)
preserveHash?: boolean;
// Locale-specific (applies to all if undefined)
locales?: string[];
// Track as legacy redirect
isLegacy?: boolean;
}For language-specific redirects:
{
from: '/politica-privacidad',
to: '/es/privacy',
locales: ['es'], // Only for Spanish users
}By default, query parameters are preserved during redirects:
/privacy-policy?utm_source=email&utm_medium=newsletter
↓
/privacy?utm_source=email&utm_medium=newsletter
Disable with preserveQuery: false:
{
from: '/old',
to: '/new',
preserveQuery: false, // Query params NOT preserved
}Hash fragments are NOT preserved by default:
/privacy-policy#data-security
↓
/privacy (hash removed)
Enable with preserveHash: true:
{
from: '/old',
to: '/new',
preserveHash: true, // Hash IS preserved
}-
Early Redirect in Middleware
- Redirects are handled at the middleware level (before route processing)
- Reduces computational overhead for legacy URLs
-
Efficient Pattern Matching
- Exact matches checked first (O(1))
- Wildcard patterns use compiled regex (O(n) worst case)
-
Rule Organization
- Most-used rules placed first for faster matching
- Separate rule sets for different categories (privacy, global, etc.)
- Redirect lookup: ~0.1-0.5ms per request
- No measurable impact on overall request time (<1% overhead)
- Limit wildcard patterns in high-traffic sections
- Use exact matches when possible
- Consider caching redirect results for frequently accessed URLs
The system validates redirect destinations:
- Only relative URLs allowed (no protocol/domain changes)
- Query parameters are URL-encoded
- No user-controlled redirect destinations
- All query parameters are URL-encoded
- Special characters properly escaped
- Array parameters handled securely
- All redirects maintain protocol (HTTPS → HTTPS)
- No downgrade from HTTPS to HTTP
- Language preference cookie preserved across redirects
- User session maintained
- Authentication state unaffected
Located in src/lib/__tests__/redirectManagement.test.ts
Coverage:
- Rule matching (exact, wildcard, locale-specific)
- Query parameter preservation
- Hash fragment handling
- HTTP status codes
- Edge cases (empty params, special characters)
Run tests:
pnpm test src/lib/__tests__/redirectManagement.test.tsLocated in src/middleware/__tests__/redirectManagement.test.ts
Coverage:
- Middleware integration
- Locale extraction from cookies and paths
- Multiple redirect chains
- Error handling
- Complex query parameters
Run tests:
pnpm test src/middleware/__tests__/redirectManagement.test.tsLocated in src/app/privacy/__tests__/privacy-page.test.tsx
Coverage:
- Privacy page rendering
- Multi-language support
- Accessibility structure
- Content sections
- Links and navigation
Run tests:
pnpm test src/app/privacy/__tests__/privacy-page.test.tsxTest redirect chains manually:
# Start dev server
pnpm dev
# Test in browser
curl -L http://localhost:3000/privacy-policy?utm_source=test
# Should redirect to:
# http://localhost:3000/privacy?utm_source=test✓ Semantic HTML
- Proper heading hierarchy (h1, h2, h3)
- Semantic article, header, footer, nav elements
role="main"on main content
✓ Navigation
- Table of contents with anchor links
- Skip links support
- Keyboard navigation (Tab, Enter)
✓ Screen Reader Support
aria-labelon main content area- Descriptive link text
- Proper
<time>tag for dates
✓ Visual Accessibility
- Sufficient color contrast (WCAG AA compliant)
- Readable font sizes (16px base)
- Proper line spacing
- Dark mode support
# Run accessibility tests
pnpm test:a11y
# Manual testing with screen reader
# macOS: VoiceOver (Cmd+F5)
# Windows: NVDA (free) or JAWS
# Web: axe DevTools extensionThe system logs all redirects for analytics:
interface RedirectLog {
timestamp: number;
from: string;
to: string;
locale?: string;
userAgent?: string;
referrer?: string;
statusCode: number;
}Extend src/lib/redirectManagement.ts:
export async function logRedirect(entry: RedirectLog): Promise<void> {
// Send to analytics service
await fetch('/api/analytics/redirects', {
method: 'POST',
body: JSON.stringify(entry),
});
}- Redirect Count: Total redirects per day
- Top Redirected URLs: Most frequently redirected from URLs
- Browser/Device: Redirect trends by browser/device
- Geographic Data: Redirect patterns by region
- Performance: Redirect impact on page load time
- Create redirect management system
- Implement Privacy Policy page
- Add unit tests
- Add integration tests
- Update middleware
- Update public-facing links to use new URLs
- Deploy changes to production
- Monitor redirect logs
- Collect performance metrics
- Track redirect usage
- Identify any broken chains
- Update analytics dashboards
- Communicate changes to stakeholders
Check:
- Rule exists in
ALL_REDIRECTS - Pathname matches rule pattern
- Locale restrictions (if any)
- Middleware is enabled
Debug:
import { shouldRedirect } from '@/lib/redirectManagement';
const should = shouldRedirect('/privacy-policy');
console.log('Should redirect:', should); // Should be trueSolution: Ensure preserveQuery: true in the redirect rule
{
from: '/old',
to: '/new',
preserveQuery: true, // Enable preservation
}Check:
- Cookie value:
i18n:language - Locale-specific rules match user's locale
- No conflicting global rules
- Wildcard support (
/old-*matches/old-page,/old-section) - Regex pattern support for complex matching
- Conditional redirects based on user role
- Redirect A/B testing
- Automatic redirect generation from sitemap
- Redirect performance dashboard
- GraphQL subscription support for redirect config changes
- Caching of redirect rules in Redis
- Real-time redirect updates without deployment
- Predictive redirect suggestions
- Automated broken link detection
- Integration with SEO tools
- Next.js Middleware Documentation
- HTTP Redirect Status Codes
- URL API Reference
- WCAG Accessibility Guidelines
For questions or issues:
- Email: privacy@teachlink.com
- Documentation: See inline code comments
- Tests: See test files for usage examples