2
0

feat(pages): improve SEO meta tags for blog posts and landing pages

Add comprehensive Open Graph and Twitter Card meta tags for blog post detail pages. Use blog post title, subtitle, and featured image when available. Add og:url and twitter:title/description tags. Set twitter:card to summary_large_image when images present, otherwise summary. Add twitter:site support from SEO config. Generates canonical page URL from request context (handles http/https based on TLS).
This commit is contained in:
2026-03-16 01:51:02 -04:00
parent 222c21bf98
commit d32bcc4d8c
2 changed files with 47 additions and 6 deletions

View File

@@ -157,6 +157,13 @@ func setupLandingPageContext(ctx *context.Context, repo *repo_model.Repository,
ctx.Data["PageIsPagesLanding"] = true
ctx.Data["RepoURL"] = repo.HTMLURL()
ctx.Data["LogoURL"] = resolveLogoURL(ctx, repo, config)
// Build canonical page URL for og:url
scheme := "https"
if ctx.Req.TLS == nil && strings.HasPrefix(ctx.Req.Host, "localhost") {
scheme = "http"
}
ctx.Data["PageURL"] = scheme + "://" + ctx.Req.Host + ctx.Req.URL.RequestURI()
ctx.Data["NumStars"] = repo.NumStars
ctx.Data["NumForks"] = repo.NumForks
ctx.Data["Year"] = time.Now().Year()

View File

@@ -3,21 +3,55 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{{if and .PageIsBlogDetail .BlogPost}}
<title>{{.BlogPost.Title}} - {{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}{{.Repository.Name}}{{end}}</title>
<meta name="description" content="{{if .BlogPost.Subtitle}}{{.BlogPost.Subtitle}}{{else}}{{.Repository.Description}}{{end}}">
{{else}}
<title>{{if .Config.Hero.Headline}}{{.Config.Hero.Headline}}{{else}}{{.Repository.Name}}{{end}} - {{.Repository.Owner.Name}}</title>
<meta name="description" content="{{if .Config.Hero.Subheadline}}{{.Config.Hero.Subheadline}}{{else}}{{.Repository.Description}}{{end}}">
<meta property="og:title" content="{{if .Config.SEO.Title}}{{.Config.SEO.Title}}{{else if .Config.Hero.Headline}}{{.Config.Hero.Headline}}{{else}}{{.Repository.Name}}{{end}}">
<meta property="og:description" content="{{if .Config.SEO.Description}}{{.Config.SEO.Description}}{{else if .Config.Hero.Subheadline}}{{.Config.Hero.Subheadline}}{{else}}{{.Repository.Description}}{{end}}">
<meta property="og:type" content="website">
{{if .Config.SEO.UseMediaKitOG}}
{{end}}
{{if and .PageIsBlogDetail .BlogPost}}
<meta property="og:title" content="{{.BlogPost.Title}}">
<meta property="og:description" content="{{if .BlogPost.Subtitle}}{{.BlogPost.Subtitle}}{{else if .Config.SEO.Description}}{{.Config.SEO.Description}}{{else}}{{.Repository.Description}}{{end}}">
<meta property="og:type" content="article">
<meta property="og:url" content="{{.PageURL}}">
<meta name="twitter:title" content="{{.BlogPost.Title}}">
<meta name="twitter:description" content="{{if .BlogPost.Subtitle}}{{.BlogPost.Subtitle}}{{else if .Config.SEO.Description}}{{.Config.SEO.Description}}{{else}}{{.Repository.Description}}{{end}}">
{{if .BlogPost.FeaturedImage}}
<meta property="og:image" content="{{.BlogPost.FeaturedImage.DownloadURL}}">
<meta name="twitter:image" content="{{.BlogPost.FeaturedImage.DownloadURL}}">
<meta name="twitter:card" content="summary_large_image">
{{else if .Config.SEO.UseMediaKitOG}}
<meta property="og:image" content="{{AppUrl}}{{.Repository.OwnerName}}/{{.Repository.Name}}/social-preview">
<meta name="twitter:image" content="{{AppUrl}}{{.Repository.OwnerName}}/{{.Repository.Name}}/social-preview">
<meta name="twitter:card" content="summary_large_image">
{{else if .Config.SEO.OGImage}}
<meta property="og:image" content="{{.Config.SEO.OGImage}}">
<meta name="twitter:image" content="{{.Config.SEO.OGImage}}">
{{end}}
{{if or .Config.SEO.UseMediaKitOG .Config.SEO.OGImage}}
<meta name="twitter:card" content="summary_large_image">
{{else}}
<meta name="twitter:card" content="summary">
{{end}}
{{else}}
<meta property="og:title" content="{{if .Config.SEO.Title}}{{.Config.SEO.Title}}{{else if .Config.Hero.Headline}}{{.Config.Hero.Headline}}{{else}}{{.Repository.Name}}{{end}}">
<meta property="og:description" content="{{if .Config.SEO.Description}}{{.Config.SEO.Description}}{{else if .Config.Hero.Subheadline}}{{.Config.Hero.Subheadline}}{{else}}{{.Repository.Description}}{{end}}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{.PageURL}}">
<meta name="twitter:title" content="{{if .Config.SEO.Title}}{{.Config.SEO.Title}}{{else if .Config.Hero.Headline}}{{.Config.Hero.Headline}}{{else}}{{.Repository.Name}}{{end}}">
<meta name="twitter:description" content="{{if .Config.SEO.Description}}{{.Config.SEO.Description}}{{else if .Config.Hero.Subheadline}}{{.Config.Hero.Subheadline}}{{else}}{{.Repository.Description}}{{end}}">
{{if .Config.SEO.UseMediaKitOG}}
<meta property="og:image" content="{{AppUrl}}{{.Repository.OwnerName}}/{{.Repository.Name}}/social-preview">
<meta name="twitter:image" content="{{AppUrl}}{{.Repository.OwnerName}}/{{.Repository.Name}}/social-preview">
<meta name="twitter:card" content="summary_large_image">
{{else if .Config.SEO.OGImage}}
<meta property="og:image" content="{{.Config.SEO.OGImage}}">
<meta name="twitter:image" content="{{.Config.SEO.OGImage}}">
<meta name="twitter:card" content="summary_large_image">
{{else}}
<meta name="twitter:card" content="summary">
{{end}}
{{end}}
{{if .Config.SEO.TwitterSite}}<meta name="twitter:site" content="{{.Config.SEO.TwitterSite}}">{{end}}
{{if .Config.SEO.Keywords}}<meta name="keywords" content="{{StringUtils.Join .Config.SEO.Keywords ","}}">{{end}}
{{if .LangSwitcherEnabled}}{{range .AvailableLanguages}}
<link rel="alternate" hreflang="{{.}}" href="?lang={{.}}">{{end}}