Skip to content

Commit d9d85e3

Browse files
committed
feat: enhance SEO and metadata across multiple pages for improved visibility and consistency
1 parent 2f4e1d3 commit d9d85e3

11 files changed

Lines changed: 252 additions & 107 deletions

File tree

Web/Pages/About.cshtml

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,46 @@
11
@page
22
@model AboutModel
3+
@inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
4+
@using Microsoft.AspNetCore.Http.Extensions
35
@{
46
ViewData["Title"] = "About - Copilot That Jawn";
57
ViewData["Description"] = "Learn about Copilot That Jawn - your comprehensive hub for Microsoft Copilot, GitHub Copilot, and AI productivity tools with a Philly flavor.";
8+
var context = HttpContextAccessor.HttpContext;
9+
var absoluteUrl = Request.GetDisplayUrl();
10+
var canonicalUrl = $"{context?.Request.Scheme}://{context?.Request.Host}/about";
11+
var logoUrl = $"{Request.Scheme}://{Request.Host}/img/icon-with-bg.webp";
12+
}
13+
14+
@section HeadMetadata {
15+
<link rel="canonical" href="@canonicalUrl" />
16+
17+
<!-- Social Media and SEO Meta Tags -->
18+
<meta property="og:title" content="About - Copilot That Jawn" />
19+
<meta property="og:description" content="Learn about Copilot That Jawn - your comprehensive hub for Microsoft Copilot, GitHub Copilot, and AI productivity tools with a Philly flavor." />
20+
<meta property="og:url" content="@absoluteUrl" />
21+
<meta property="og:type" content="website" />
22+
<meta property="og:site_name" content="Copilot That Jawn" />
23+
<meta property="og:image" content="@logoUrl" />
24+
<meta property="og:image:width" content="1200" />
25+
<meta property="og:image:height" content="630" />
26+
27+
<!-- Twitter Card tags -->
28+
<meta name="twitter:card" content="summary_large_image" />
29+
<meta name="twitter:title" content="About - Copilot That Jawn" />
30+
<meta name="twitter:description" content="Learn about Copilot That Jawn - your comprehensive hub for Microsoft Copilot, GitHub Copilot, and AI productivity tools with a Philly flavor." />
31+
<meta name="twitter:image" content="@logoUrl" />
32+
<meta name="twitter:site" content="@("@CopilotThatJawn")" />
33+
34+
<!-- Additional SEO -->
35+
<meta name="robots" content="index, follow" />
36+
<meta name="keywords" content="copilot, ai tools, microsoft copilot, github copilot, productivity, philadelphia tech, about us" />
637
}
738

839
<div class="container py-5">
940
<div class="row justify-content-center">
10-
<div class="col-lg-8"> <div class="text-center mb-5"> <img src="~/img/icon.webp" alt="Copilot That Jawn Logo" class="header-icon mb-4" />
41+
<div class="col-lg-8">
42+
<div class="text-center mb-5">
43+
<img src="~/img/icon.webp" alt="Copilot That Jawn Logo" class="header-icon mb-4" />
1144
<h1 class="display-4 fw-bold mb-4 brand-title">About Copilot That Jawn</h1>
1245
<p class="lead text-muted">Where Philly Innovation Meets AI Excellence</p>
1346
</div>

Web/Pages/Contribute.cshtml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,39 @@
11
@page
22
@model ContributeModel
3+
@inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
4+
@using Microsoft.AspNetCore.Http.Extensions
35
@{
46
ViewData["Title"] = "Contribute - Copilot That Jawn";
57
ViewData["Description"] = "Learn how to contribute tips, tricks, and insights to Copilot That Jawn by submitting pull requests to our GitHub repository.";
8+
var context = HttpContextAccessor.HttpContext;
9+
var absoluteUrl = Request.GetDisplayUrl();
10+
var canonicalUrl = $"{context?.Request.Scheme}://{context?.Request.Host}/contribute";
11+
var logoUrl = $"{Request.Scheme}://{Request.Host}/img/icon-with-bg.webp";
12+
}
13+
14+
@section HeadMetadata {
15+
<link rel="canonical" href="@canonicalUrl" />
16+
17+
<!-- Social Media and SEO Meta Tags -->
18+
<meta property="og:title" content="Contribute to Copilot That Jawn" />
19+
<meta property="og:description" content="Learn how to contribute tips, tricks, and insights to Copilot That Jawn by submitting pull requests to our GitHub repository." />
20+
<meta property="og:url" content="@absoluteUrl" />
21+
<meta property="og:type" content="website" />
22+
<meta property="og:site_name" content="Copilot That Jawn" />
23+
<meta property="og:image" content="@logoUrl" />
24+
<meta property="og:image:width" content="1200" />
25+
<meta property="og:image:height" content="630" />
26+
27+
<!-- Twitter Card tags -->
28+
<meta name="twitter:card" content="summary_large_image" />
29+
<meta name="twitter:title" content="Contribute to Copilot That Jawn" />
30+
<meta name="twitter:description" content="Learn how to contribute tips, tricks, and insights to Copilot That Jawn by submitting pull requests to our GitHub repository." />
31+
<meta name="twitter:image" content="@logoUrl" />
32+
<meta name="twitter:site" content="@("@CopilotThatJawn")" />
33+
34+
<!-- Additional SEO -->
35+
<meta name="robots" content="index, follow" />
36+
<meta name="keywords" content="contribute, copilot, ai tools, github, pull requests, open source, collaboration, content creation" />
637
}
738

839
<div class="container py-5">

Web/Pages/Index.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
<h1 class="display-3 fw-bold mb-3 brand-title">Copilot That Jawn</h1>
4444
<p class="lead fs-4 mb-4">Where Philly innovation meets AI excellence. Master Microsoft Copilot and GitHub Copilot with our expert-curated tips.</p>
4545
<div class="d-flex flex-wrap gap-2"> <a asp-page="/Tips/Index" class="btn btn-light btn-lg">Browse All Tips</a>
46-
<a asp-page="/Tips/Category" asp-route-category="GitHub Copilot" class="btn btn-outline-light btn-lg">GitHub Copilot</a>
46+
<a asp-page="/Tips/Category" asp-route-category="github copilot" class="btn btn-outline-light btn-lg">GitHub Copilot</a>
4747
</div>
4848
</div> <div class="col-lg-5 col-md-4 text-center">
4949
<img src="~/img/icon.webp" alt="Copilot That Jawn Logo" class="img-fluid" style="max-height: 200px;" />

Web/Pages/Shared/_Layout.cshtml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@
117117
gtag('config', '@Configuration["GoogleAnalytics:MeasurementId"]');
118118
</script>
119119
</environment>
120+
121+
<!-- SEO metadata and canonical URLs -->
122+
@await RenderSectionAsync("HeadMetadata", required: false)
120123
</head>
121124
<body>
122125
<header>

Web/Pages/Tips/Category.cshtml

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,38 @@
11
@page "/tips/category/{category}"
22
@model Web.Pages.Tips.CategoryModel
3+
@inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
4+
@using Microsoft.AspNetCore.Http.Extensions
35
@{
46
ViewData["Title"] = $"{Model.Category} Tips & Tricks";
7+
var context = HttpContextAccessor.HttpContext;
8+
var canonicalUrl = $"{context?.Request.Scheme}://{context?.Request.Host}/tips/category/{Model.Category.ToLowerInvariant()}";
9+
var absoluteUrl = Request.GetDisplayUrl();
10+
var logoUrl = $"{Request.Scheme}://{Request.Host}/img/icon-with-bg.webp";
11+
}
12+
13+
@section HeadMetadata {
14+
<link rel="canonical" href="@canonicalUrl" />
15+
16+
<!-- Social Media and SEO Meta Tags -->
17+
<meta property="og:title" content="@Model.Category Tips & Tricks - Copilot That Jawn" />
18+
<meta property="og:description" content="Explore tips and tricks in the @Model.Category category for Microsoft Copilot, GitHub Copilot, and AI productivity tools." />
19+
<meta property="og:url" content="@absoluteUrl" />
20+
<meta property="og:type" content="website" />
21+
<meta property="og:site_name" content="Copilot That Jawn" />
22+
<meta property="og:image" content="@logoUrl" />
23+
<meta property="og:image:width" content="1200" />
24+
<meta property="og:image:height" content="630" />
25+
26+
<!-- Twitter Card tags -->
27+
<meta name="twitter:card" content="summary_large_image" />
28+
<meta name="twitter:title" content="@Model.Category Tips & Tricks - Copilot That Jawn" />
29+
<meta name="twitter:description" content="Explore tips and tricks in the @Model.Category category for Microsoft Copilot, GitHub Copilot, and AI productivity tools." />
30+
<meta name="twitter:image" content="@logoUrl" />
31+
<meta name="twitter:site" content="@("@CopilotThatJawn")" />
32+
33+
<!-- Additional SEO -->
34+
<meta name="robots" content="index, follow" />
35+
<meta name="category" content="@Model.Category" />
536
}
637

738
<div class="container-fluid">
@@ -45,8 +76,7 @@
4576
</div>
4677
<div class="card-body category-filter-body">
4778
@foreach (var cat in Model.ViewModel.Categories)
48-
{
49-
<a asp-page="/Tips/Category" asp-route-category="@cat"
79+
{ <a asp-page="/Tips/Category" asp-route-category="@cat.ToLowerInvariant()"
5080
class="list-group-item list-group-item-action border-0 @(cat == Model.Category ? "active" : "")">
5181
@cat @if (cat == Model.Category)
5282
{
@@ -162,27 +192,24 @@
162192
<ul class="pagination justify-content-center">
163193
@if (Model.PageNumber > 1)
164194
{
165-
<li class="page-item">
166-
<a class="page-link" asp-page="/Tips/Category"
167-
asp-route-category="@Model.Category"
195+
<li class="page-item"> <a class="page-link" asp-page="/Tips/Category"
196+
asp-route-category="@Model.Category.ToLowerInvariant()"
168197
asp-route-page="@(Model.PageNumber - 1)">Previous</a>
169198
</li>
170199
}
171200

172201
@for (int i = Math.Max(1, Model.PageNumber - 2); i <= Math.Min(Model.ViewModel.TotalPages, Model.PageNumber + 2); i++)
173202
{
174-
<li class="page-item @(i == Model.PageNumber ? "active" : "")">
175-
<a class="page-link" asp-page="/Tips/Category"
176-
asp-route-category="@Model.Category"
203+
<li class="page-item @(i == Model.PageNumber ? "active" : "")"> <a class="page-link" asp-page="/Tips/Category"
204+
asp-route-category="@Model.Category.ToLowerInvariant()"
177205
asp-route-page="@i">@i</a>
178206
</li>
179207
}
180208

181209
@if (Model.PageNumber < Model.ViewModel.TotalPages)
182210
{
183-
<li class="page-item">
184-
<a class="page-link" asp-page="/Tips/Category"
185-
asp-route-category="@Model.Category"
211+
<li class="page-item"> <a class="page-link" asp-page="/Tips/Category"
212+
asp-route-category="@Model.Category.ToLowerInvariant()"
186213
asp-route-page="@(Model.PageNumber + 1)">Next</a>
187214
</li>
188215
}

Web/Pages/Tips/Category.cshtml.cs

Lines changed: 82 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -9,82 +9,86 @@ namespace Web.Pages.Tips;
99
[OutputCache(Duration = 21600, Tags = new[] { "tips", "content", "category" })]
1010
public class CategoryModel : BasePageModel
1111
{
12-
private readonly IContentService _contentService;
13-
private readonly ILogger<CategoryModel> _logger;
14-
// Use default cache duration for category pages (6 hours) - categories change infrequently
15-
// protected override int CacheDurationSeconds => base.CacheDurationSeconds; // 6 hours default
16-
17-
public CategoryModel(IContentService contentService, ILogger<CategoryModel> logger)
18-
{
19-
_contentService = contentService;
20-
_logger = logger;
21-
}
22-
23-
public TipListViewModel ViewModel { get; set; } = new();
24-
25-
[BindProperty(SupportsGet = true)]
26-
public string Category { get; set; } = string.Empty; [BindProperty(SupportsGet = true)]
27-
public int PageNumber { get; set; } = 1;
28-
29-
public async Task<IActionResult> OnGetAsync()
30-
{
31-
if (string.IsNullOrEmpty(Category))
32-
{
33-
return RedirectToPage("/Tips/Index");
34-
}
35-
36-
try
37-
{
38-
// Get filter options first to validate category
39-
var categories = await _contentService.GetCategoriesAsync();
40-
var tags = await _contentService.GetTagsAsync();
41-
42-
// Find the matching category with correct casing
43-
var matchingCategory = categories.FirstOrDefault(c =>
44-
c.Equals(Category, StringComparison.OrdinalIgnoreCase));
45-
46-
// If category doesn't exist, redirect to index
47-
if (matchingCategory == null)
48-
{
49-
return RedirectToPage("/Tips/Index");
50-
}
51-
52-
// If category exists but with different casing, redirect to correct casing
53-
if (matchingCategory != Category)
54-
{
55-
return RedirectToPage("/Tips/Category", new { category = matchingCategory });
56-
}
57-
58-
var request = new TipSearchRequest
59-
{
60-
Category = Category,
61-
Page = Math.Max(1, PageNumber),
62-
PageSize = 12
63-
};
64-
65-
var tips = await _contentService.SearchTipsAsync(request);
66-
var totalCount = tips.Count;
67-
68-
ViewModel = new TipListViewModel
69-
{
70-
Tips = tips,
71-
Categories = categories,
72-
Tags = tags,
73-
SelectedCategory = Category,
74-
Page = PageNumber,
75-
PageSize = request.PageSize,
76-
TotalCount = totalCount
77-
};
78-
79-
ViewData["Title"] = $"{Category} Tips & Tricks";
80-
ViewData["Description"] = $"Discover tips and tricks in the {Category} category for Microsoft Copilot, GitHub Copilot, Azure AI, and more AI productivity tools.";
81-
82-
return Page();
83-
}
84-
catch (Exception ex)
85-
{
86-
_logger.LogError(ex, "Error loading tips for category: {Category}", Category);
87-
return RedirectToPage("/Error");
88-
}
89-
}
12+
private readonly IContentService _contentService;
13+
private readonly ILogger<CategoryModel> _logger;
14+
// Use default cache duration for category pages (6 hours) - categories change infrequently
15+
// protected override int CacheDurationSeconds => base.CacheDurationSeconds; // 6 hours default
16+
17+
public CategoryModel(IContentService contentService, ILogger<CategoryModel> logger)
18+
{
19+
_contentService = contentService;
20+
_logger = logger;
21+
}
22+
23+
public TipListViewModel ViewModel { get; set; } = new();
24+
25+
[BindProperty(SupportsGet = true)]
26+
public string Category { get; set; } = string.Empty;
27+
28+
[BindProperty(SupportsGet = true)]
29+
public int PageNumber { get; set; } = 1;
30+
31+
public async Task<IActionResult> OnGetAsync()
32+
{
33+
if (string.IsNullOrEmpty(Category))
34+
{
35+
return RedirectToPage("/Tips/Index");
36+
}
37+
38+
try
39+
{
40+
// Get filter options first to validate category
41+
var categories = await _contentService.GetCategoriesAsync();
42+
var tags = await _contentService.GetTagsAsync();
43+
44+
// Find the matching category with correct casing
45+
var matchingCategory = categories.FirstOrDefault(c =>
46+
c.Equals(Category, StringComparison.OrdinalIgnoreCase));
47+
48+
// If category doesn't exist, redirect to index
49+
if (matchingCategory == null)
50+
{
51+
return RedirectToPage("/Tips/Index");
52+
}
53+
54+
// Always redirect to lowercase version of the category for SEO consistency
55+
if (matchingCategory.ToLowerInvariant() != Category.ToLowerInvariant())
56+
{
57+
return RedirectToPage("/Tips/Category", new { category = matchingCategory.ToLowerInvariant() });
58+
}
59+
60+
Category = matchingCategory;
61+
62+
var request = new TipSearchRequest
63+
{
64+
Category = Category,
65+
Page = Math.Max(1, PageNumber),
66+
PageSize = 12
67+
};
68+
69+
var tips = await _contentService.SearchTipsAsync(request);
70+
var totalCount = tips.Count;
71+
72+
ViewModel = new TipListViewModel
73+
{
74+
Tips = tips,
75+
Categories = categories,
76+
Tags = tags,
77+
SelectedCategory = Category,
78+
Page = PageNumber,
79+
PageSize = request.PageSize,
80+
TotalCount = totalCount
81+
};
82+
83+
ViewData["Title"] = $"{Category} Tips & Tricks";
84+
ViewData["Description"] = $"Discover tips and tricks in the {Category} category for Microsoft Copilot, GitHub Copilot, Azure AI, and more AI productivity tools.";
85+
86+
return Page();
87+
}
88+
catch (Exception ex)
89+
{
90+
_logger.LogError(ex, "Error loading tips for category: {Category}", Category);
91+
return RedirectToPage("/Error");
92+
}
93+
}
9094
}

Web/Pages/Tips/Details.cshtml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
@if (!string.IsNullOrEmpty(Model.ViewModel.Tip.Category))
5454
{
5555
<li class="breadcrumb-item">
56-
<a asp-page="/Tips/Category" asp-route-category="@Model.ViewModel.Tip.Category">@Model.ViewModel.Tip.Category</a>
56+
<a asp-page="/Tips/Category" asp-route-category="@Model.ViewModel.Tip.Category.ToLowerInvariant()">@Model.ViewModel.Tip.Category</a>
5757
</li>
5858
}
5959
<li class="breadcrumb-item active" aria-current="page">@Model.ViewModel.Tip.Title</li>
@@ -104,8 +104,7 @@
104104
{
105105
<div class="d-flex flex-wrap gap-1">
106106
@foreach (var tag in Model.ViewModel.Tip.Tags)
107-
{
108-
<a asp-page="/Tips/Tag" asp-route-tag="@tag"
107+
{ <a asp-page="/Tips/Tag" asp-route-tag="@tag.ToLowerInvariant()"
109108
class="badge bg-light text-dark text-decoration-none">#@tag</a>
110109
}
111110
</div>

0 commit comments

Comments
 (0)