feat: add metadata#35
Conversation
There was a problem hiding this comment.
Pull request overview
Adds SEO/social-sharing metadata to the site layout so links render rich previews (Open Graph + Twitter cards), addressing issue #28.
Changes:
- Introduces resolved title/description defaults for pages missing frontmatter values.
- Adds canonical URL, Open Graph tags, and Twitter card tags (including image URL resolution).
- Updates the document
<title>to use the resolved title.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| currentPath = currentPath.slice(0, -1); | ||
| } | ||
|
|
||
| const pageUrl = Astro.url.toString(); |
There was a problem hiding this comment.
currentPath is normalized to remove a trailing slash, but pageUrl (used for canonical/og:url/twitter:url) is taken directly from Astro.url.toString() and can still include the trailing slash (and any search/hash). This can lead to canonical/OG URLs that don’t match the normalized route and can produce inconsistent embeds/SEO. Consider constructing pageUrl from Astro.url after you normalize currentPath (and clearing search/hash) so the canonical and og:url reflect the same normalized URL.
| const pageUrl = Astro.url.toString(); | |
| const normalizedPageUrl = new URL(Astro.url); | |
| normalizedPageUrl.pathname = currentPath; | |
| normalizedPageUrl.search = ""; | |
| normalizedPageUrl.hash = ""; | |
| const pageUrl = normalizedPageUrl.toString(); |
now built from cloned Astro.url after currentPath normalization with search and hash cleared
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 3 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return new Response(png, { | ||
| headers: { | ||
| "Content-Type": "image/png", | ||
| "Cache-Control": "public, max-age=31536000, immutable", |
There was a problem hiding this comment.
Cache-Control: public, max-age=31536000, immutable will cause clients/CDNs to cache these OG images for a year even though the URL is not content-addressed (it won’t change when title/description/category change). Consider removing immutable and/or using a shorter max-age (or s-maxage with revalidation) so metadata image updates propagate after redeploys.
| "Cache-Control": "public, max-age=31536000, immutable", | |
| "Cache-Control": "public, max-age=3600", |
| const title = frontmatter.title as string; | ||
| const description = frontmatter.description as string; | ||
| const defaultTitle = "cmu.guide"; | ||
| const defaultDescription = | ||
| "Your go-to guide for navigating the Tartan life, academics, and beyond!"; | ||
| const resolvedTitle = title?.trim() || defaultTitle; | ||
| const resolvedDescription = description?.trim() || defaultDescription; |
There was a problem hiding this comment.
frontmatter.title as string / frontmatter.description as string are cast to string, but the code now treats them as optional (via ?.trim() and title && ...). It would be safer to type them as string | undefined (or avoid the cast) so TypeScript can correctly model when title/description are absent.
|
|
should be good to go! |


Closes #28