Page Management System Documentation
Overview
This system provides a backend-only, API-first approach to managing website pages with comprehensive SEO capabilities. The API is the single source of truth for page definitions, allowing pages to be added or updated from the CRM without redeploying the frontend.
Architecture
Key Features
- Frontend-Agnostic Design: API can be consumed by any frontend framework (React, Vue, Next.js, etc.)
- SEO-First: Comprehensive SEO metadata including OpenGraph, Twitter Cards, and structured data
- Redirect Management: Built-in 301/302 redirect handling for SEO-friendly URL changes
- Multi-Language Support: Full internationalization with translation system
- Content Scheduling: Schedule page publication and expiration
- Hierarchical Structure: Parent-child page relationships for navigation
- Analytics: Built-in page view tracking and redirect statistics
Data Models
Page Model
- Core Fields: slug, path, title, content, active status
- SEO: Complete meta tags, OpenGraph, Twitter Cards, canonical URLs, structured data
- Indexation: Control search engine indexing and sitemap inclusion
- Relationships: Parent/child pages, related pages
- Scheduling: Publication and expiration dates
- Analytics: View count and last viewed timestamp
Redirect Model
- Configuration: Source path, target path, status code (301/302)
- Matching: Exact, prefix, or regex pattern matching
- Validation: Automatic loop detection and chain warnings
- Analytics: Hit count and last hit timestamp
- Expiration: Optional expiration for temporary redirects
API Endpoints
Public Endpoints (Frontend)
Pages
POST /page/published - Get all published pages
GET /page/path/:path - Get page by path (primary routing endpoint)
GET /page/seo/:path - Get SEO metadata only (lightweight)
GET /page/slug/:slug - Get page by slug
GET /page/sitemap - Get sitemap data
GET /page/hierarchy/:id - Get page hierarchy (breadcrumbs)
POST /page/search - Full-text search
GET /page/:id - Get page by ID
Redirects
GET /redirect/resolve/:path - Resolve redirect (404 handling)
GET /redirect/check/:path - Check if redirect exists
GET /redirect/active - Get all active redirects
GET /redirect/stats - Get redirect statistics
GET /redirect/top - Get most used redirects
GET /redirect/:id - Get redirect by ID
Protected Endpoints (Admin/CRM)
Pages
POST /page - Get all pages (with filters)
POST /page/new - Create page
PUT /page/edit/:id - Update page
DELETE /page/delete - Delete pages
PUT /page/toggle - Toggle active status
PUT /page/seo/bulk - Bulk update SEO
Redirects
POST /redirect - Get all redirects (with filters)
POST /redirect/validate-chain - Validate redirect chain
POST /redirect/new - Create redirect
POST /redirect/bulk - Bulk create redirects
PUT /redirect/edit/:id - Update redirect
DELETE /redirect/delete - Delete redirects
PUT /redirect/toggle - Toggle active status
DELETE /redirect/clean-expired - Clean expired redirects
Frontend Integration
Basic Page Resolution
// Frontend router (Next.js example)
export async function getServerSideProps(context) {
const path = context.resolvedUrl;
const moduleId = process.env.MODULE_ID;
try {
// 1. Try to fetch page
const pageResponse = await fetch(
`${API_URL}/page/path${path}?module_id=${moduleId}`
);
if (pageResponse.ok) {
const page = await pageResponse.json();
return { props: { page } };
}
// 2. If 404, check for redirect
const redirectResponse = await fetch(
`${API_URL}/redirect/resolve${path}?module_id=${moduleId}`
);
if (redirectResponse.ok) {
const redirect = await redirectResponse.json();
return {
redirect: {
destination: redirect.targetPath,
permanent: redirect.statusCode === 301,
},
};
}
// 3. No page or redirect found
return { notFound: true };
} catch (error) {
console.error('Error resolving page:', error);
return { notFound: true };
}
}
SEO Meta Tags
// Component for rendering SEO meta tags
import Head from 'next/head';
export function SeoHead({ page }) {
const { seo, path, title } = page;
return (
<Head>
{/* Basic Meta Tags */}
<title>{seo.metaTitle || title}</title>
<meta name="description" content={seo.metaDescription} />
{seo.metaKeywords && (
<meta name="keywords" content={seo.metaKeywords.join(', ')} />
)}
<meta name="robots" content={seo.robots || 'index,follow'} />
{/* Canonical URL */}
{seo.canonicalUrl && <link rel="canonical" href={seo.canonicalUrl} />}
{/* Alternate Language URLs */}
{seo.alternateUrls?.map((alt) => (
<link
key={alt.lang}
rel="alternate"
hreflang={alt.lang}
href={alt.url}
/>
))}
{/* OpenGraph Tags */}
<meta property="og:title" content={seo.ogTitle || title} />
<meta property="og:description" content={seo.ogDescription} />
<meta property="og:type" content={seo.ogType || 'website'} />
{seo.ogImage && <meta property="og:image" content={seo.ogImage} />}
{/* Twitter Cards */}
<meta name="twitter:card" content={seo.twitterCard || 'summary'} />
{seo.twitterSite && (
<meta name="twitter:site" content={seo.twitterSite} />
)}
{seo.twitterCreator && (
<meta name="twitter:creator" content={seo.twitterCreator} />
)}
<meta name="twitter:title" content={seo.twitterTitle || title} />
<meta name="twitter:description" content={seo.twitterDescription} />
{seo.twitterImage && (
<meta name="twitter:image" content={seo.twitterImage} />
)}
{/* Structured Data (JSON-LD) */}
{seo.structuredData && (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(seo.structuredData),
}}
/>
)}
</Head>
);
}
Sitemap Generation
// Generate sitemap.xml from API
export async function generateSitemap() {
const moduleId = process.env.MODULE_ID;
const baseUrl = process.env.WEBSITE_URL;
const response = await fetch(
`${API_URL}/page/sitemap?module_id=${moduleId}`
);
const { items } = await response.json();
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${items
.map(
(page) => `
<url>
<loc>${baseUrl}${page.path}</loc>
<lastmod>${new Date(page.updatedAt).toISOString()}</lastmod>
<changefreq>${page.sitemap.changefreq}</changefreq>
<priority>${page.sitemap.priority}</priority>
</url>`
)
.join('')}
</urlset>`;
return sitemap;
}
Best Practices
URL Structure
- Use Clean URLs:
/about-us,/products/shoes,/blog/article-title - Lowercase Only: All paths are automatically lowercased
- Hyphens for Spaces: Use kebab-case for slugs
- Language Prefixes:
/en/about-us,/fr/a-propos - Hierarchical Structure: Use parentPage relationships for breadcrumbs
SEO Optimization
- Meta Title: 50-60 characters (displayed in search results)
- Meta Description: 150-160 characters (search result snippet)
- Canonical URLs: Always set canonical URL to avoid duplicate content
- Alternate Languages: Specify all language versions for international SEO
- Structured Data: Add Schema.org markup for rich snippets
- Sitemap Priority:
- Homepage: 1.0
- Main sections: 0.8
- Standard pages: 0.5
- Archive pages: 0.3
Redirect Management
- Use 301 for Permanent Changes: Transfers SEO value to new URL
- Use 302 for Temporary Changes: Preserves SEO value of original URL
- Avoid Redirect Chains: Redirect directly to final destination
- Monitor Redirect Stats: Identify frequently used redirects
- Clean Expired Redirects: Regularly run cleanup maintenance
Performance
- Use Path Lookups: Primary endpoint
/page/path/:pathis indexed - Lightweight SEO Endpoint: Use
/page/seo/:pathfor meta tags only - Cache Published Pages: Frontend can cache published pages
- Pagination: Use pagination for large page lists
- Select Fields: Only fetch fields you need
Content Management Workflow
Adding a New Page
- Create in CRM: Use
/page/newendpoint - Set Active to False: Start as draft
- Configure SEO: Add meta tags, OpenGraph, etc.
- Preview: Frontend can preview drafts with auth
- Publish: Set active to true
- Monitor: Check analytics and adjust SEO
Changing a URL
- Create Redirect: Before changing URL, create 301 redirect
- Update Page Path: Change page path to new URL
- Update Alternate URLs: Update language versions if applicable
- Test Redirect: Verify old URL redirects correctly
- Monitor Hits: Track redirect usage
Multi-Language Pages
- Create Base Page: Create page in default language
- Clone for Languages: Create translated versions
- Set Translation Refs: Link pages via translation field
- Set Alternate URLs: Configure hreflang tags
- Consistent Paths: Use language prefixes (/en/, /fr/)
Maintenance Tasks
Scheduled Publishing
// Background job to activate scheduled pages
export async function activateScheduledPages() {
const now = new Date();
await Page.updateMany(
{
active: false,
scheduledAt: { $lte: now },
},
{
$set: { active: true },
}
);
}
// Run every hour
cron.schedule('0 * * * *', activateScheduledPages);
Expire Old Pages
// Background job to deactivate expired pages
export async function deactivateExpiredPages() {
const now = new Date();
await Page.updateMany(
{
active: true,
expiresAt: { $lte: now },
},
{
$set: { active: false },
}
);
}
// Run every hour
cron.schedule('0 * * * *', deactivateExpiredPages);
Clean Expired Redirects
// Use built-in endpoint
await fetch(`${API_URL}/redirect/clean-expired?module_id=${moduleId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
});
// Run daily
cron.schedule('0 0 * * *', cleanExpiredRedirects);
Security Considerations
- Authentication: All write operations require JWT authentication
- Authorization: Use role-based access (commonSlave, etc.)
- Validation: Input validation on all endpoints
- Rate Limiting: Implement rate limiting on public endpoints
- XSS Protection: Sanitize HTML content before rendering
- CSRF Protection: Use CSRF tokens for state-changing operations
Monitoring & Analytics
Page Analytics
- View count per page
- Last viewed timestamp
- Popular pages report
- SEO performance tracking
Redirect Analytics
- Hit count per redirect
- Most used redirects
- Redirect chains detection
- Expired redirects report
Example Use Cases
E-commerce Product Pages
const productPage = {
module_id: '...',
slug: 'running-shoes-nike',
path: '/products/running-shoes-nike',
title: 'Nike Running Shoes - Professional Sports Gear',
pageType: 'standard',
active: true,
indexed: true,
seo: {
metaTitle: 'Nike Running Shoes | Buy Online',
metaDescription: 'Professional Nike running shoes with advanced cushioning...',
structuredData: {
'@context': 'https://schema.org',
'@type': 'Product',
name: 'Nike Running Shoes',
offers: {
'@type': 'Offer',
price: '129.99',
priceCurrency: 'USD',
},
},
},
sitemap: {
include: true,
priority: 0.8,
changefreq: 'weekly',
},
};
Blog Article
const blogArticle = {
module_id: '...',
slug: 'seo-best-practices-2025',
path: '/blog/seo-best-practices-2025',
title: 'SEO Best Practices for 2025',
pageType: 'article',
active: true,
indexed: true,
seo: {
metaTitle: 'SEO Best Practices for 2025 | Complete Guide',
metaDescription: 'Learn the latest SEO techniques and best practices...',
ogType: 'article',
structuredData: {
'@context': 'https://schema.org',
'@type': 'Article',
headline: 'SEO Best Practices for 2025',
author: {
'@type': 'Person',
name: 'John Doe',
},
datePublished: '2025-01-15',
},
},
sitemap: {
include: true,
priority: 0.7,
changefreq: 'monthly',
},
};
URL Migration with Redirects
// Old URL structure: /page.php?id=123
// New URL structure: /about-us
const redirect = {
module_id: '...',
sourcePath: '/page.php',
targetPath: '/about-us',
statusCode: 301,
matchType: 'exact',
active: true,
reason: 'Migrated from old PHP site to new structure',
};
Troubleshooting
Page Not Found
- Check
activestatus - Check
scheduledAt(may not be published yet) - Check
expiresAt(may have expired) - Verify
pathformat (must start with /) - Check
indexedif using indexation filter
Redirect Not Working
- Verify
activestatus - Check
expiresAt - Test
matchType(exact vs prefix vs regex) - Check
caseSensitivesetting - Verify no redirect loops exist
SEO Issues
- Verify meta tags are populated
- Check canonical URL configuration
- Ensure alternate URLs are set for all languages
- Validate structured data JSON-LD
- Check robots.txt and sitemap.xml
Support
For issues or questions:
- Review API documentation
- Check error logs
- Verify authentication tokens
- Test endpoints with Postman/Insomnia
- Monitor database indexes