Page Structure Design
Guidance for designing page hierarchies, templates, and modular page composition systems for headless CMS.
When to Use This Skill
- Designing page tree structures
- Creating page templates with zones
- Implementing page builder functionality
- Planning sitemap generation
- Building modular page composition
Page Hierarchy Patterns
Basic Page Tree
public class Page
{
public Guid Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Slug { get; set; } = string.Empty;
// Hierarchy
public Guid? ParentId { get; set; }
public Page? Parent { get; set; }
public List<Page> Children { get; set; } = new();
// Computed path
public string Path { get; set; } = string.Empty; // /about/team/leadership
public int Depth { get; set; }
public int Order { get; set; }
// Template and content
public string Template { get; set; } = string.Empty;
public PageContent Content { get; set; } = new();
}
Page Sets (Collections)
// Page set for grouping related pages
public class PageSet
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Slug { get; set; } = string.Empty;
public PageSetType Type { get; set; }
// Configuration
public string ItemTemplate { get; set; } = string.Empty;
public string ListTemplate { get; set; } = string.Empty;
public int ItemsPerPage { get; set; } = 10;
// URL pattern
public string UrlPattern { get; set; } = string.Empty; // /blog/{slug}
}
public enum PageSetType
{
Blog, // Chronological posts
Portfolio, // Project showcase
Team, // Team members
Products, // Product catalog
FAQ, // Q&A collection
Custom // User-defined
}
Template System
Template Definition
public class PageTemplate
{
public string Name { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
// Template hierarchy
public string? ParentTemplate { get; set; }
// Available zones for content
public List<TemplateZone> Zones { get; set; } = new();
// Required fields
public List<TemplateField> Fields { get; set; } = new();
// Applicable page types
public List<string> ApplicablePageTypes { get; set; } = new();
}
public class TemplateZone
{
public string Name { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public ZoneType Type { get; set; }
public List<string> AllowedWidgets { get; set; } = new();
public int? MaxWidgets { get; set; }
}
public enum ZoneType
{
Single, // One widget only
Multiple, // Multiple widgets stacked
Grid // Grid layout
}
Template Inheritance
BaseTemplate
├── Zones: Header, Footer, Sidebar
└── Fields: MetaTitle, MetaDescription
├── HomeTemplate (extends Base)
│ └── Zones: Hero, Features, CTA
│
├── ContentTemplate (extends Base)
│ └── Zones: MainContent, RelatedContent
│
└── LandingTemplate (extends Base)
└── Zones: Hero, Sections (multiple)
Page Builder Components
Widget System
public abstract class Widget
{
public Guid Id { get; set; }
public string Type { get; set; } = string.Empty;
public int Order { get; set; }
public Dictionary<string, object?> Settings { get; set; } = new();
}
public class TextWidget : Widget
{
public string Content { get; set; } = string.Empty;
}
public class ImageWidget : Widget
{
public Guid MediaItemId { get; set; }
public string? Alt { get; set; }
public string? Caption { get; set; }
}
public class CallToActionWidget : Widget
{
public string Heading { get; set; } = string.Empty;
public string? Subheading { get; set; }
public string ButtonText { get; set; } = string.Empty;
public string ButtonUrl { get; set; } = string.Empty;
public string? BackgroundImageId { get; set; }
}
public class CardGridWidget : Widget
{
public List<Card> Cards { get; set; } = new();
public int Columns { get; set; } = 3;
}
Page Content Structure
public class PageContent
{
// Zone-based content storage
public Dictionary<string, List<Widget>> Zones { get; set; } = new();
// Page-level fields
public string? HeroTitle { get; set; }
public string? HeroSubtitle { get; set; }
public Guid? HeroImageId { get; set; }
// SEO
public string? MetaTitle { get; set; }
public string? MetaDescription { get; set; }
public bool NoIndex { get; set; }
}
Sitemap Generation
Sitemap Data Model
public class SitemapEntry
{
public string Url { get; set; } = string.Empty;
public DateTime LastModified { get; set; }
public ChangeFrequency ChangeFrequency { get; set; }
public decimal Priority { get; set; }
public List<SitemapAlternate>? Alternates { get; set; }
}
public class SitemapAlternate
{
public string Hreflang { get; set; } = string.Empty;
public string Url { get; set; } = string.Empty;
}
public enum ChangeFrequency
{
Always,
Hourly,
Daily,
Weekly,
Monthly,
Yearly,
Never
}
Sitemap Generation Service
public class SitemapService
{
public async Task<List<SitemapEntry>> GenerateSitemapAsync()
{
var entries = new List<SitemapEntry>();
// Add pages
var pages = await _pageRepository.GetPublishedPagesAsync();
foreach (var page in pages)
{
entries.Add(new SitemapEntry
{
Url = $"{_baseUrl}{page.Path}",
LastModified = page.ModifiedUtc,
ChangeFrequency = GetChangeFrequency(page),
Priority = CalculatePriority(page)
});
}
// Add page set items (blog posts, products, etc.)
var pageSets = await _pageSetRepository.GetAllAsync();
foreach (var pageSet in pageSets)
{
var items = await _pageSetRepository.GetItemsAsync(pageSet.Id);
foreach (var item in items)
{
var url = GenerateUrl(pageSet.UrlPattern, item);
entries.Add(new SitemapEntry
{
Url = url,
LastModified = item.ModifiedUtc,
ChangeFrequency = ChangeFrequency.Weekly,
Priority = 0.6m
});
}
}
return entries;
}
private decimal CalculatePriority(Page page)
{
// Home page highest priority
if (page.Depth == 0) return 1.0m;
// Decrease by depth
return Math.Max(0.5m, 1.0m - (page.Depth * 0.1m));
}
}
Page Tree API
REST Endpoints
GET /api/pages # Root pages
GET /api/pages/{id} # Single page
GET /api/pages/{id}/children # Child pages
GET /api/pages/path/{*path} # Page by URL path
GET /api/pages/tree # Full page tree
GET /api/sitemap.xml # XML sitemap
GET /api/sitemap.json # JSON sitemap
Page Tree Response
{
"data": {
"id": "page-123",
"title": "About Us",
"slug": "about",
"path": "/about",
"template": "ContentTemplate",
"depth": 1,
"order": 2,
"children": [
{
"id": "page-456",
"title": "Our Team",
"slug": "team",
"path": "/about/team",
"template": "TeamTemplate",
"children": []
},
{
"id": "page-789",
"title": "Careers",
"slug": "careers",
"path": "/about/careers",
"template": "ContentTemplate",
"children": []
}
]
},
"breadcrumbs": [
{ "title": "Home", "path": "/" },
{ "title": "About Us", "path": "/about" }
]
}
Best Practices
Page Structure
| Pattern |
When to Use |
| Flat structure |
Simple sites, few pages |
| 2-level hierarchy |
Most corporate sites |
| Deep hierarchy |
Documentation, large catalogs |
| Page sets |
Blog, portfolio, team pages |
Template Design
DO:
- Keep zones semantic (Header, MainContent, Sidebar)
- Allow flexible widget placement
- Inherit common zones from base template
- Provide sensible defaults
DON'T:
- Create overly specific templates
- Hard-code layout in templates
- Mix content and presentation concerns
- Create deep template inheritance chains
Related Skills
navigation-architecture - Menu and breadcrumb design
url-routing-patterns - URL structure and routing
content-type-modeling - Page as content type