abp
abp copied to clipboard
CmsKit: Meta information for SEO
Is there an existing issue for this?
- [X] I have searched the existing issues
Is your feature request related to a problem? Please describe the problem.
I think it would be a good addition if blog posts and pages get the ability of adding some meta information to be more SEO friendly.
For example:
Description
Take short description and put that into <meta name="description" content="YOURSHORTDESCRIPTION" />
I think a configuration would be good to control whether to apply the logic or not. Maybe the user has something else in place.
Keywords
Take tags and put that into <meta name="keywords" content="YOURTAG1,YOURTAG2" />
I think a configuration would be good to control whether to apply the logic or not. Maybe the user has something else in place.
Title
Take the Title and set ViewBag.Title to that
I think this would need something additional. Maybe you want to have something like "CompanyName - " + EntityTitle
Describe the solution you'd like
The maybe-overkill solution
There would be multiple ways of archiving this. Here's just an idea here to get discussion going.
Add a new page as a submenu item of CMS for meta information. The new page prodives CRUD functionality for setting meta stuff like described above. The entity could look something like this:
- string input -> EntityType (set if you want to apply logic for a specific entity type. leave empty to apply to all)
- dropdown input -> MetaAttribute enum info (Title, Description, Keywords, CustomMetaProperty) where the first three are as described above
- string input -> if CustomMetaProperty, you can set things like
og:titleif you want to support Open Graph stuff - dropdown input -> dropdown for supported properties to set + the option
SetManually
If SetManually is set -> You need to manually define the attribute when creating/ editing the specified type.
Related components would need to respect what's been set here.
Additional context
No response
This is a very important functionality for professional blogposts. SEO is almost make or brake for sites so this is very important.
Is there a workaround @EngincanV?
This is a very important functionality for professional blogposts. SEO is almost make or brake for sites so this is very important.
Is there a workaround @EngincanV?
I agree that SEO is absolutely important, especially in such content (blogposts). There are some workaround that you might use but they are not effective and are cumbersome, to be honest, at least the ways I thought.
I think we can prioritize this issue. I'll talk with the team, and we'll evaluate this.
OK we'll plan to implement this feature
Any Update on this
Any Update on this
We haven't prioritized it yet.
To further formulate this issue so that a developer has more info on this:
some things that would make sense to define every time:
- If the page/ article should be indexed by search engines or not
- Meta title aka. ViewBag.Title
- Meta description aka ViewBag.MetaDescription
optional but strongly emphasized:
Apart from that, something like this could make sense: New CRUD entity with
- string MetaName
- bool IsRequired
- string content
which is then just displayed in a new tab when creating/ updating a page/ blog post
packages that may be of interest here: https://github.com/PureKrome/SimpleSitemap
some pretty retarted example but here you go (having a controller in out Web.Public)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ConiDerp.Cms;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using SimpleSiteMap;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Timing;
using Volo.CmsKit.Public.Blogs;
namespace ConiDerp.Web.Public.Controllers;
public class SitemapController : Controller
{
private const string _defaultBlogSlug = "default";
private readonly string _siteUrl;
private readonly IConfiguration _configuration;
private readonly IBlogPostPublicAppService _blogPostPublicAppService;
private readonly IConiCmsHelperAppService _coniCmsHelperAppService;
private readonly IClock _clock;
public SitemapController(
IBlogPostPublicAppService blogPostPublicAppService,
IConfiguration configuration,
IClock clock,
IConiCmsHelperAppService coniCmsHelperAppService)
{
_blogPostPublicAppService = blogPostPublicAppService;
_configuration = configuration;
_siteUrl = _configuration["App:SelfUrl"].EnsureEndsWith('/');
_clock = clock;
_coniCmsHelperAppService = coniCmsHelperAppService;
}
/// <summary>
/// simplistic sitemap just to have something to start with
/// <para/>
/// for more info see <see href="https://programmingcsharp.com/tips-asp-net-seo/#Headers_Meta_tags_and_other_HTML_tags">here</see> or <see href="https://github.com/PureKrome/SimpleSitemap/wiki/sitemap-index-example">here</see>
/// </summary>
/// <returns></returns>
[Route("/sitemap.xml")]
public async Task<IActionResult> Sitemap()
{
List<SitemapNode> nodes =
[
new SitemapNode(GetStaticSiteUrl(string.Empty), DateTime.Now),
new SitemapNode(GetStaticSiteUrl("contact-us"), DateTime.Now),
new SitemapNode(GetStaticSiteUrl("imprint"), DateTime.Now),
new SitemapNode(GetStaticSiteUrl("privacy-policy"), DateTime.Now),
new SitemapNode(GetStaticSiteUrl("privacy-policy-de"), DateTime.Now),
new SitemapNode(GetStaticSiteUrl("software-consulting"), DateTime.Now),
new SitemapNode(GetStaticSiteUrl("software-development"), DateTime.Now),
new SitemapNode(GetStaticSiteUrl("software-extension"), DateTime.Now),
.. await GetBlogPostsAsync(),
.. await GetPagesAsync(),
];
var sitemapService = new SitemapService();
var xml = sitemapService.ConvertToXmlUrlset(nodes);
return Content(xml, "application/xml");
}
private Uri GetStaticSiteUrl(string site)
{
return new Uri($"{_siteUrl}{site}");
}
private Uri GetBlogPostUrl(string blogPostSlug)
{
// sample: https://mysite.com/blogs/default/hello-world
return new Uri($"{_siteUrl}blogs/{_defaultBlogSlug}/{blogPostSlug}");
}
private Uri GetPageUrl(string pageSlug)
{
// sample: https://mysite.com/hello-world
return new Uri($"{_siteUrl}{pageSlug}");
}
private async Task<List<SitemapNode>> GetBlogPostsAsync()
{
try
{
BlogPostGetListInput input = new()
{
MaxResultCount = LimitedResultRequestDto.MaxMaxResultCount,
SkipCount = 0,
AuthorId = null,
TagId = null,
Sorting = "LastModificationTime DESC",
};
var blogPosts = await _blogPostPublicAppService.GetListAsync(_defaultBlogSlug, input);
List<SitemapNode> nodes = blogPosts.Items.Select(p => new SitemapNode(
url: GetBlogPostUrl(p.Slug),
lastModified: p.LastModificationTime ?? _clock.Now)
).ToList();
return nodes;
}
catch
{
return [];
}
}
private async Task<List<SitemapNode>> GetPagesAsync()
{
try
{
PagedAndSortedResultRequestDto input = new()
{
MaxResultCount = LimitedResultRequestDto.MaxMaxResultCount,
SkipCount = 0,
Sorting = "LastModificationTime DESC",
};
var blogPosts = await _coniCmsHelperAppService.GetPagesAsync(input);
List<SitemapNode> nodes = blogPosts.Items.Select(p => new SitemapNode(
url: GetPageUrl(p.Slug),
lastModified: p.LastModificationTime ?? _clock.Now)
).ToList();
return nodes;
}
catch
{
return [];
}
}
}
https://github.com/karl-sjogren/robots-txt-middleware
sample usage in *WebPublicModule:
private static void ConfigureRobotsTxtCore(ServiceConfigurationContext context, IConfiguration configuration)
{
// https://programmingcsharp.com/configure-robots-txt-in-asp-net-core/
context.Services.AddStaticRobotsTxt(b =>
{
b
.AddSection(section => section
.AddUserAgent("Googlebot")
.Allow("/"))
.AddSection(section => section
.AddUserAgent("Bingbot")
.Allow("/"))
.AddSitemap($"{configuration["App:SelfUrl"].EnsureEndsWith('/')}sitemap.xml");
return b;
});
}
The goal is to have something like this - but dynamically generated.
sample of how something like this looks in piranha.core: