Release notes for JekCMS v1.5.3 covering enhanced Schema.org markup for Article, Organization, BreadcrumbList and FAQ, automatic Open Graph image generation, LCP optimization with fetchpriority, new admin dashboard widgets, and critical bug fixes.
What Is in Version 1.5.3
This release focuses on three areas that directly affect search visibility: structured data, social sharing previews, and page load performance. We also shipped five new admin dashboard widgets and fixed eight bugs reported by the community since v1.5.0.
Every change in this release has been tested across our network of 12 production sites running JekCMS. The SEO improvements were validated through Google Search Console and Rich Results Testing tool before this release.
Enhanced Schema.org Markup
Previous versions of JekCMS included basic WebSite and Article schema. Version 1.5.3 expands this significantly with four schema types that directly trigger rich results in Google Search.
Article Schema Improvements
The Article schema now includes every field Google recommends for news and blog articles:
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Post Title Here",
"description": "Post excerpt...",
"image": {
"@type": "ImageObject",
"url": "https://site.com/uploads/images/featured.avif",
"width": 1200,
"height": 630
},
"author": {
"@type": "Person",
"name": "Author Name",
"url": "https://site.com/author/slug"
},
"publisher": {
"@type": "Organization",
"name": "Site Name",
"logo": {
"@type": "ImageObject",
"url": "https://site.com/assets/images/logo.svg"
}
},
"datePublished": "2026-03-29T10:00:00+03:00",
"dateModified": "2026-03-29T14:30:00+03:00",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://site.com/post-slug"
},
"wordCount": 2450,
"articleSection": "Category Name"
}
New fields in v1.5.3: wordCount, articleSection, proper ImageObject with dimensions (previously just a URL string), and dateModified (previously missing, which caused Google to show stale dates in search results).
Organization Schema
Every page now includes Organization schema in the <head> section, populated from Admin > Settings:
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Site Name",
"url": "https://site.com",
"logo": "https://site.com/assets/images/logo.svg",
"sameAs": [
"https://twitter.com/handle",
"https://facebook.com/page",
"https://linkedin.com/company/name"
],
"contactPoint": {
"@type": "ContactPoint",
"email": "info@site.com",
"contactType": "customer service"
}
}
The sameAs array pulls from the social media URLs configured in Settings > Social. If a social URL is empty, it is excluded from the array rather than included as an empty string (which would cause a validation error).
BreadcrumbList Schema
Breadcrumb markup was previously visual only (HTML). Now it includes structured data that Google uses to show breadcrumb trails in search results:
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://site.com/"
},
{
"@type": "ListItem",
"position": 2,
"name": "Blog",
"item": "https://site.com/blog"
},
{
"@type": "ListItem",
"position": 3,
"name": "Post Title"
}
]
}
Note: The last item does not include an item URL per Google's specification — it represents the current page.
FAQ Schema
Posts that contain a FAQ section (using the [faq] shortcode or manually structured with <h3> question/answer pairs) now automatically generate FAQPage schema. The parser scans the content for question-answer patterns and generates the structured data:
function extract_faq_schema(string $content): ?array {
// Match H3 questions followed by paragraph answers
preg_match_all(
'/<h3[^>]*>(.+?)</h3>s*<p>(.+?)</p>/s',
$content,
$matches,
PREG_SET_ORDER
);
// Minimum 3 Q&A pairs with 50+ char answers
$faqs = [];
foreach ($matches as $match) {
$answer = strip_tags($match[2]);
if (mb_strlen($answer) >= 50) {
$faqs[] = [
'question' => strip_tags($match[1]),
'answer' => $answer
];
}
}
if (count($faqs) < 3) return null;
return [
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => array_map(function($faq) {
return [
'@type' => 'Question',
'name' => $faq['question'],
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => $faq['answer']
]
];
}, $faqs)
];
}
We require a minimum of 3 FAQ items with answers of at least 50 characters each. This prevents thin FAQ schema from being generated for posts that just happen to have a few short Q&A patterns.
Automatic Open Graph Image Generation
When a post does not have a featured image, social media previews look broken — just a title with no visual. Version 1.5.3 adds automatic OG image generation using PHP GD.
The system generates a 1200x630 image with:
- The site's brand color as background (pulled from Settings)
- The post title rendered in the configured font
- The site name and logo in the corner
- The category name as a subtle label
class OgImageGenerator {
private int $width = 1200;
private int $height = 630;
public function generate(array $post): string {
$img = imagecreatetruecolor($this->width, $this->height);
// Brand color background
$bgColor = $this->hexToRgb(get_setting('brand_color') ?: '#1a1a2e');
$bg = imagecolorallocate($img, $bgColor[0], $bgColor[1], $bgColor[2]);
imagefill($img, 0, 0, $bg);
// Title text
$white = imagecolorallocate($img, 255, 255, 255);
$fontPath = ROOT_PATH . '/assets/fonts/OpenSans-Bold.ttf';
$title = $post['title'];
// Word wrap the title
$fontSize = 42;
$maxWidth = $this->width - 120;
$lines = $this->wordWrap($title, $fontPath, $fontSize, $maxWidth);
$y = 200;
foreach ($lines as $line) {
imagettftext($img, $fontSize, 0, 60, $y, $white, $fontPath, $line);
$y += 60;
}
// Site name at bottom
$gray = imagecolorallocate($img, 180, 180, 180);
imagettftext($img, 20, 0, 60, $this->height - 40, $gray, $fontPath,
get_setting('site_name'));
// Save as JPEG (better compatibility than AVIF for social platforms)
$path = ROOT_PATH . '/uploads/og/' . $post['slug'] . '.jpg';
imagejpeg($img, $path, 90);
imagedestroy($img);
return 'uploads/og/' . $post['slug'] . '.jpg';
}
}
Generated images are cached — they are only regenerated when the post title changes. The meta tag automatically switches between the featured image and the generated OG image:
$ogImage = !empty($post['featured_image'])
? UPLOADS_URL . '/' . $post['featured_image']
: SITE_URL . '/' . generate_og_image($post);
echo '<meta property="og:image" content="' . $ogImage . '" />';
echo '<meta property="og:image:width" content="1200" />';
echo '<meta property="og:image:height" content="630" />';
LCP Optimization with fetchpriority
The Largest Contentful Paint (LCP) element on most blog pages is the featured image. Version 1.5.3 automatically adds fetchpriority="high" to the LCP image and ensures it is never lazy-loaded.
The template helpers now accept an $isLCP parameter:
// Before v1.5.3
echo get_featured_picture($post, 'large');
// After v1.5.3
echo get_featured_picture($post, 'large', true); // true = this is the LCP image
When $isLCP is true, the function outputs fetchpriority="high" and omits loading="lazy". When false (the default), it outputs loading="lazy" as before.
Across our 12 production sites, this single change reduced the average mobile LCP by 320ms. The improvement is most noticeable on slower connections (3G/4G) where resource prioritization has a larger impact.
New Admin Dashboard Widgets
The admin dashboard now includes five new at-a-glance widgets:
- Content Calendar: A monthly view showing scheduled and published posts. Click any day to see the posts for that date.
- SEO Health: Shows the count of posts missing meta descriptions, alt text, or schema markup. Red/yellow/green indicators for quick scanning.
- Performance Snapshot: Displays the latest Core Web Vitals from the Chrome UX Report API (if configured). Updates daily.
- Recent Comments: Shows the five most recent comments awaiting moderation, with one-click approve/reject.
- Disk Usage: Shows uploads directory size breakdown by folder (images, media, og, general) with a progress bar against the hosting quota.
All widgets load asynchronously via AJAX after the dashboard page renders, so they do not slow down the initial page load. Each widget can be collapsed or hidden through the dashboard settings.
Bug Fixes
- Fixed: Duplicate canonical tags appearing when both the theme header and the SEO helper outputted canonical tags. The helper now checks if a canonical tag already exists in the output buffer.
- Fixed: FAQ schema generating with fewer than 3 items, which caused Google validation warnings. Now enforces the 3-item minimum.
- Fixed: Open Graph
og:urlincluding query parameters (pagination, sort). Now strips all query parameters from the OG URL. - Fixed: Breadcrumb schema using relative URLs instead of absolute URLs. All
itemURLs now include the full domain. - Fixed: Author link in schema pointing to a 404 page when the author did not have a public profile. Now falls back to the site homepage.
- Fixed:
dateModifiedin Article schema always showing the same value asdatePublished. Now correctly reads theupdated_atcolumn. - Fixed: RSS feed
<pubDate>not including timezone offset, causing some feed readers to show incorrect times. - Fixed: Media library upload failing silently when the
uploads/subdirectory for the current month did not exist. Now auto-creates the directory with proper permissions.
Upgrade Instructions
From v1.5.0 or later:
- Back up your database and files
- Replace all files in
classes/,includes/, andadmin/with the v1.5.3 versions - No database migration needed — this release has no schema changes
- Clear your cache: Admin > Settings > Clear All Cache
- Verify Schema markup with Google's Rich Results Test on a few pages
From versions earlier than v1.5.0, follow the v1.5.0 migration guide first, then apply this update.