feat: improve blog

This commit is contained in:
Oleg Proskurin 2026-01-21 19:19:18 +07:00
parent fd89bc424e
commit 8b1487e743
6 changed files with 55 additions and 8 deletions

View File

@ -12,6 +12,33 @@ import {
BlogSidebar, BlogSidebar,
BlogShareButtons, BlogShareButtons,
} from '../_components'; } from '../_components';
import type { BlogPost } from '../types';
const generateJsonLd = (post: BlogPost) => ({
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
description: post.description,
image: `https://banatie.app${post.heroImage}`,
datePublished: post.date,
dateModified: post.date,
author: {
'@type': 'Person',
name: post.author.name,
},
publisher: {
'@type': 'Organization',
name: 'Banatie',
logo: {
'@type': 'ImageObject',
url: 'https://banatie.app/banatie-logo.png',
},
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://banatie.app/blog/${post.slug}/`,
},
});
interface PageProps { interface PageProps {
params: Promise<{ slug: string }>; params: Promise<{ slug: string }>;
@ -41,8 +68,13 @@ export default async function BlogPostPage({ params }: PageProps) {
const relatedArticles = getPostsBySlugs(post.relatedArticles); const relatedArticles = getPostsBySlugs(post.relatedArticles);
return ( return (
<main> <>
<BlogPostHeader post={post} /> <script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(generateJsonLd(post)) }}
/>
<main id="main-content">
<BlogPostHeader post={post} />
<div className="bg-white border-t-0 -mt-1 pt-12 lg:pt-16 pb-12"> <div className="bg-white border-t-0 -mt-1 pt-12 lg:pt-16 pb-12">
<div className="container mx-auto px-4 sm:px-6 lg:px-8"> <div className="container mx-auto px-4 sm:px-6 lg:px-8">
@ -72,6 +104,7 @@ export default async function BlogPostPage({ params }: PageProps) {
</div> </div>
</div> </div>
</div> </div>
</main> </main>
</>
); );
} }

View File

@ -25,6 +25,7 @@ export const BlogImage = ({
alt={alt} alt={alt}
placeholder="blur" placeholder="blur"
className="w-full h-auto" className="w-full h-auto"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 66vw, 800px"
/> />
) : ( ) : (
<div className="relative aspect-video bg-gray-100"> <div className="relative aspect-video bg-gray-100">
@ -33,6 +34,7 @@ export const BlogImage = ({
alt={alt} alt={alt}
fill fill
className="object-cover" className="object-cover"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 66vw, 800px"
/> />
</div> </div>
)} )}

View File

@ -23,13 +23,13 @@ export const BlogShareButtons = ({ url, title }: BlogShareButtonsProps) => {
const linkedinUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`; const linkedinUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`;
return ( return (
<div className="sticky top-28 flex flex-col gap-4 items-center"> <div className="sticky top-38 flex flex-col gap-4 items-center">
<a <a
href={twitterUrl} href={twitterUrl}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
aria-label="Share on Twitter" aria-label="Share on Twitter"
className="p-2 rounded-full bg-white text-gray-500 hover:text-violet-500 transition-colors border border-gray-200 shadow-sm" className="p-3 rounded-full bg-white text-gray-500 hover:text-violet-500 transition-colors border border-gray-200 shadow-sm"
> >
<svg className="w-5 h-5 fill-current" viewBox="0 0 24 24"> <svg className="w-5 h-5 fill-current" viewBox="0 0 24 24">
<path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z" /> <path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z" />
@ -41,7 +41,7 @@ export const BlogShareButtons = ({ url, title }: BlogShareButtonsProps) => {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
aria-label="Share on LinkedIn" aria-label="Share on LinkedIn"
className="p-2 rounded-full bg-white text-gray-500 hover:text-blue-600 transition-colors border border-gray-200 shadow-sm" className="p-3 rounded-full bg-white text-gray-500 hover:text-blue-600 transition-colors border border-gray-200 shadow-sm"
> >
<svg className="w-5 h-5 fill-current" viewBox="0 0 24 24"> <svg className="w-5 h-5 fill-current" viewBox="0 0 24 24">
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z" /> <path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z" />
@ -51,7 +51,7 @@ export const BlogShareButtons = ({ url, title }: BlogShareButtonsProps) => {
<button <button
onClick={handleCopyLink} onClick={handleCopyLink}
aria-label="Copy Link" aria-label="Copy Link"
className="p-2 rounded-full bg-white text-gray-500 hover:text-gray-900 transition-colors border border-gray-200 shadow-sm" className="p-3 rounded-full bg-white text-gray-500 hover:text-gray-900 transition-colors border border-gray-200 shadow-sm"
> >
<LinkIcon className="w-5 h-5" /> <LinkIcon className="w-5 h-5" />
</button> </button>

View File

@ -44,6 +44,8 @@ export const metadata: Metadata = {
twitter: { twitter: {
card: 'summary_large_image', card: 'summary_large_image',
site: '@BanatieApp',
creator: '@BanatieApp',
title: 'Blog | Banatie', title: 'Blog | Banatie',
description: description:
'Articles, guides, and updates about AI-powered image generation.', 'Articles, guides, and updates about AI-powered image generation.',
@ -60,7 +62,7 @@ export default function BlogPage() {
return ( return (
<> <>
<BlogBackground /> <BlogBackground />
<main className="flex-grow bg-transparent relative z-10 pt-10 pb-12 lg:pt-16 lg:pb-20"> <main id="main-content" className="flex-grow bg-transparent relative z-10 pt-10 pb-12 lg:pt-16 lg:pb-20">
<div className="container mx-auto px-4 sm:px-6 lg:px-8"> <div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12"> <div className="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12">
<div className="lg:col-span-8 xl:col-span-9"> <div className="lg:col-span-8 xl:col-span-9">

View File

@ -46,6 +46,8 @@ export const generatePostMetadata = (post: BlogPost): Metadata => ({
twitter: { twitter: {
card: 'summary_large_image', card: 'summary_large_image',
site: '@BanatieApp',
creator: '@BanatieApp',
title: post.title, title: post.title,
description: post.description, description: post.description,
images: [post.heroImage], images: [post.heroImage],

View File

@ -8,6 +8,14 @@ export default function LandingsLayout({
}) { }) {
return ( return (
<> <>
{/* Skip to content link for accessibility */}
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-[100] focus:px-4 focus:py-2 focus:bg-violet-600 focus:text-white focus:rounded-lg focus:outline-none focus:ring-2 focus:ring-violet-400"
>
Skip to content
</a>
{/* Sticky Header */} {/* Sticky Header */}
<header className="sticky top-0 z-50 bg-slate-900/80 backdrop-blur-md border-b border-white/5"> <header className="sticky top-0 z-50 bg-slate-900/80 backdrop-blur-md border-b border-white/5">
<nav className="max-w-7xl mx-auto px-4 sm:px-6 py-2 sm:py-3 flex justify-between items-center h-12 sm:h-14 md:h-16"> <nav className="max-w-7xl mx-auto px-4 sm:px-6 py-2 sm:py-3 flex justify-between items-center h-12 sm:h-14 md:h-16">