feat: enhance SEO and structured data across multiple components; update site description and improve sitemap and RSS feed
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
BreadcrumbSeparator,
|
||||
} from '@/components/ui/breadcrumb'
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import { SITE } from '@/consts'
|
||||
|
||||
export interface BreadcrumbItem {
|
||||
href?: string
|
||||
@@ -20,8 +21,30 @@ interface Props {
|
||||
}
|
||||
|
||||
const { items } = Astro.props
|
||||
|
||||
const breadcrumbStructuredData = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"name": "Home",
|
||||
"item": SITE.href
|
||||
},
|
||||
...items.map((item, index) => ({
|
||||
"@type": "ListItem",
|
||||
"position": index + 2,
|
||||
"name": item.label,
|
||||
...(item.href && { "item": new URL(item.href, SITE.href).toString() })
|
||||
}))
|
||||
]
|
||||
}
|
||||
---
|
||||
|
||||
<!-- Breadcrumb Structured Data -->
|
||||
<script type="application/ld+json" is:inline set:html={JSON.stringify(breadcrumbStructuredData)} />
|
||||
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
|
||||
@@ -11,19 +11,29 @@ import Favicons from './Favicons.astro'
|
||||
content="width=device-width, initial-scale=1.0, user-scalable=yes"
|
||||
/>
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" />
|
||||
<meta name="googlebot" content="index, follow" />
|
||||
<meta name="google" content="notranslate" />
|
||||
<meta name="revisit-after" content="1 days" />
|
||||
|
||||
<!-- SEO Enhancement Tags -->
|
||||
<meta name="author" content={SITE.author} />
|
||||
<meta name="publisher" content={SITE.title} />
|
||||
<meta name="language" content={SITE.locale} />
|
||||
<meta name="geo.region" content="RO" />
|
||||
<meta name="geo.placename" content={SITE.location} />
|
||||
|
||||
<!-- Mobile and PWA Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content={SITE.title} />
|
||||
<meta
|
||||
name="format-detection"
|
||||
content="telephone=no,date=no,address=no,email=no,url=no"
|
||||
/>
|
||||
|
||||
<!-- Theme Colors -->
|
||||
<meta
|
||||
name="theme-color"
|
||||
content="#121212"
|
||||
@@ -31,16 +41,22 @@ import Favicons from './Favicons.astro'
|
||||
/>
|
||||
<meta
|
||||
name="theme-color"
|
||||
content="#121212"
|
||||
content="#ffffff"
|
||||
media="(prefers-color-scheme: light)"
|
||||
/>
|
||||
<meta name="msapplication-TileColor" content="#121212" />
|
||||
|
||||
<!-- Prefetch and Preconnect for Performance -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin />
|
||||
<link rel="dns-prefetch" href="//fonts.googleapis.com" />
|
||||
|
||||
<!-- Sitemap and Feed Links -->
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title={SITE.title}
|
||||
title={`${SITE.title} RSS Feed`}
|
||||
href={new URL('rss.xml', Astro.site)}
|
||||
/>
|
||||
|
||||
|
||||
@@ -9,60 +9,91 @@ interface Props {
|
||||
const { title = SITE.title, description = SITE.description } = Astro.props
|
||||
const image = new URL('/ogImage.png', Astro.site).toString()
|
||||
const posts = await getAllPosts()
|
||||
|
||||
// Optimize description for SEO
|
||||
const optimizedDescription = description.length > 160
|
||||
? description.substring(0, 157) + '...'
|
||||
: description
|
||||
|
||||
// Create proper page title
|
||||
const pageTitle = title === SITE.title ? SITE.title : `${title} | ${SITE.title}`
|
||||
---
|
||||
|
||||
<title>{`${SITE.title} - ${title}`}</title>
|
||||
<meta name="description" content={description} />
|
||||
<title>{pageTitle}</title>
|
||||
<meta name="description" content={optimizedDescription} />
|
||||
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" />
|
||||
<meta name="language" content={SITE.locale} />
|
||||
<link rel="canonical" href={Astro.url} />
|
||||
|
||||
<!-- Open Graph Meta Tags -->
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:description" content={optimizedDescription} />
|
||||
<meta property="og:image" content={image} />
|
||||
<meta property="og:image:alt" content={title} />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:locale" content={SITE.locale} />
|
||||
<meta property="og:site_name" content={SITE.title} />
|
||||
<meta property="og:url" content={Astro.url} />
|
||||
|
||||
<!-- Twitter Card Meta Tags -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:description" content={optimizedDescription} />
|
||||
<meta name="twitter:image" content={image} />
|
||||
<meta name="twitter:image:alt" content={title} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:creator" content={SITE.author} />
|
||||
<meta name="twitter:site" content={SITE.href} />
|
||||
<meta name="twitter:domain" content={SITE.href} />
|
||||
<meta name="twitter:creator" content={`@${SITE.author.replace(' ', '').toLowerCase()}`} />
|
||||
<meta name="twitter:site" content={`@${SITE.author.replace(' ', '').toLowerCase()}`} />
|
||||
<meta name="twitter:domain" content={new URL(SITE.href).hostname} />
|
||||
|
||||
<!-- Enhanced JSON-LD Structured Data -->
|
||||
<script
|
||||
type="application/ld+json"
|
||||
is:inline
|
||||
set:html={`{
|
||||
set:html={JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"@id": "${SITE.href}#website",
|
||||
"url": "${SITE.href}",
|
||||
"name": "${SITE.title}",
|
||||
"description": "${description}",
|
||||
"@id": `${SITE.href}#website`,
|
||||
"url": SITE.href,
|
||||
"name": SITE.title,
|
||||
"description": optimizedDescription,
|
||||
"inLanguage": SITE.locale,
|
||||
"publisher": {
|
||||
"@type": "Person",
|
||||
"name": SITE.author,
|
||||
"url": SITE.href
|
||||
},
|
||||
"potentialAction": {
|
||||
"@type": "SearchAction",
|
||||
"target": "${SITE.href}/search?q={search_term_string}",
|
||||
"target": {
|
||||
"@type": "EntryPoint",
|
||||
"urlTemplate": `${SITE.href}/blog?q={search_term_string}`
|
||||
},
|
||||
"query-input": "required name=search_term_string"
|
||||
},
|
||||
"blogPosts": [
|
||||
${posts
|
||||
.map(
|
||||
(post) => `{
|
||||
"mainEntity": {
|
||||
"@type": "Blog",
|
||||
"@id": `${SITE.href}/blog#blog`,
|
||||
"name": `${SITE.title} Blog`,
|
||||
"description": "Technology blog covering web development, programming, and software engineering topics",
|
||||
"url": `${SITE.href}/blog`,
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": SITE.author
|
||||
},
|
||||
"blogPost": posts.slice(0, 10).map(post => ({
|
||||
"@type": "BlogPosting",
|
||||
"headline": "${post.data.title}",
|
||||
"description": "${post.data.description}",
|
||||
"url": "${SITE.href}/blog/${post.id}",
|
||||
"datePublished": "/blog/${post.id}",
|
||||
"headline": post.data.title,
|
||||
"description": post.data.description,
|
||||
"url": `${SITE.href}/blog/${post.id}/`,
|
||||
"datePublished": post.data.date.toISOString(),
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "${SITE.author}"
|
||||
}
|
||||
}`
|
||||
)
|
||||
.join(',')}
|
||||
]
|
||||
}`}
|
||||
"name": post.data.authors ? post.data.authors[0] : SITE.author
|
||||
},
|
||||
"keywords": post.data.tags ? post.data.tags.join(', ') : ''
|
||||
}))
|
||||
}
|
||||
})}
|
||||
/>
|
||||
|
||||
@@ -10,36 +10,56 @@ const { post } = Astro.props
|
||||
|
||||
const title = post.data.title || SITE.title
|
||||
const description = post.data.description || SITE.description
|
||||
const postUrl = new URL(post.id, SITE.href).toString()
|
||||
const image = SITE.href + '/image/' + post.id + '.png';
|
||||
const postUrl = new URL(`/blog/${post.id}/`, SITE.href).toString()
|
||||
const image = new URL(`/image/${post.id}.png`, SITE.href).toString()
|
||||
const author = post.data.authors ? post.data.authors.join(', ') : SITE.author
|
||||
|
||||
const optimizedDescription = description.length > 160
|
||||
? description.substring(0, 157) + '...'
|
||||
: description
|
||||
|
||||
const seoTitle = `${title} - ${SITE.title}`
|
||||
const postUrlWithoutTrailingSlash = postUrl.endsWith('/') ? postUrl.slice(0, -1) : postUrl
|
||||
const postCanonicalUrl = new URL(postUrlWithoutTrailingSlash, SITE.href).toString()
|
||||
---
|
||||
|
||||
<title>{`${title} - ${SITE.title}`}</title>
|
||||
<meta name="description" content={description} />
|
||||
<title>{seoTitle}</title>
|
||||
<meta name="description" content={optimizedDescription} />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" />
|
||||
<meta name="author" content={author} />
|
||||
<meta name="publisher" content={SITE.title} />
|
||||
<meta name="language" content={SITE.locale} />
|
||||
<link rel="canonical" href={postCanonicalUrl} />
|
||||
{post?.data.tags && <meta name="keywords" content={post.data.tags.join(', ')} />}
|
||||
|
||||
<!-- Open Graph Meta Tags -->
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:description" content={optimizedDescription} />
|
||||
<meta property="og:image" content={image} />
|
||||
<meta property="og:image:alt" content={title} />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:locale" content={SITE.locale} />
|
||||
<meta property="og:site_name" content={SITE.title} />
|
||||
<meta property="og:url" content={postUrl} />
|
||||
<meta property="og:author" content={author} />
|
||||
|
||||
<!-- Article specific Open Graph -->
|
||||
<meta property="article:published_time" content={post.data.date.toISOString()} />
|
||||
<meta property="article:modified_time" content={post.data.date.toISOString()} />
|
||||
<meta property="article:published" content={post.data.date.toISOString()} />
|
||||
<meta property="article:author" content={author} />
|
||||
<meta property="article:publisher" content={SITE.title} />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta property="twitter:image" content={image} />
|
||||
<meta name="twitter:image:alt" content={title} />
|
||||
<meta property="article:section" content="Technology" />
|
||||
|
||||
<!-- Twitter Card Meta Tags -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:creator" content={author} />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={optimizedDescription} />
|
||||
<meta name="twitter:image" content={image} />
|
||||
<meta name="twitter:image:alt" content={title} />
|
||||
<meta name="twitter:creator" content={`@${SITE.author.replace(' ', '').toLowerCase()}`} />
|
||||
<meta name="twitter:site" content={`@${SITE.author.replace(' ', '').toLowerCase()}`} />
|
||||
|
||||
{
|
||||
post?.data.tags &&
|
||||
@@ -48,22 +68,32 @@ const author = post.data.authors ? post.data.authors.join(', ') : SITE.author
|
||||
})
|
||||
}
|
||||
|
||||
<!-- Enhanced JSON-LD Structured Data -->
|
||||
<script type="application/ld+json" is:inline set:html={JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BlogPosting",
|
||||
"headline": title,
|
||||
"description": description,
|
||||
"image": image,
|
||||
"description": optimizedDescription,
|
||||
"image": {
|
||||
"@type": "ImageObject",
|
||||
"url": image,
|
||||
"width": 1200,
|
||||
"height": 630
|
||||
},
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": author
|
||||
"name": author,
|
||||
"url": SITE.href
|
||||
},
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
"name": SITE.title,
|
||||
"url": SITE.href,
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": new URL("/favicon.ico", SITE.href).toString()
|
||||
"url": new URL("/favicon.ico", SITE.href).toString(),
|
||||
"width": 32,
|
||||
"height": 32
|
||||
}
|
||||
},
|
||||
"datePublished": post.data.date.toISOString(),
|
||||
@@ -73,5 +103,15 @@ const author = post.data.authors ? post.data.authors.join(', ') : SITE.author
|
||||
"@id": postUrl
|
||||
},
|
||||
"keywords": post?.data.tags ? post.data.tags.join(', ') : '',
|
||||
"url": postUrl
|
||||
"url": postUrl,
|
||||
"inLanguage": SITE.locale,
|
||||
"isPartOf": {
|
||||
"@type": "Blog",
|
||||
"@id": new URL("/blog/", SITE.href).toString(),
|
||||
"name": `${SITE.title} Blog`
|
||||
},
|
||||
"about": post?.data.tags ? post.data.tags.map(tag => ({
|
||||
"@type": "Thing",
|
||||
"name": tag
|
||||
})) : []
|
||||
})} />
|
||||
@@ -80,25 +80,6 @@ const Navbar = () => {
|
||||
4: { width: '50%' },
|
||||
}
|
||||
|
||||
const menuVariants = {
|
||||
closed: {
|
||||
opacity: 0,
|
||||
y: "-100%",
|
||||
transition: {
|
||||
duration: 0.5,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}
|
||||
},
|
||||
open: {
|
||||
opacity: 1,
|
||||
y: "0%",
|
||||
transition: {
|
||||
duration: 0.5,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<motion.header
|
||||
@@ -201,7 +182,6 @@ const Navbar = () => {
|
||||
initial="closed"
|
||||
animate="open"
|
||||
exit="closed"
|
||||
variants={menuVariants}
|
||||
className="fixed inset-0 z-20 flex flex-col items-center justify-start bg-background border-0 shadow-none"
|
||||
>
|
||||
<div className="flex flex-col items-center justify-start h-full pt-24 w-full p-6">
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { IconMap, SocialLink, Site } from '@/types'
|
||||
export const SITE: Site = {
|
||||
title: 'Cojocaru David',
|
||||
description:
|
||||
"I'm a Junior Full Stack Developer with a passion for creating web applications. I have experience in both front-end and back-end development, and I'm always eager to learn new technologies and improve my skills. I enjoy collaborating with teams and contributing to projects that make a difference.",
|
||||
"Junior Full Stack Developer specializing in modern web technologies. Expert in React, Node.js, TypeScript, and cloud development. Read my latest tech tutorials, project insights, and programming tips on web development, DevOps, and software engineering best practices.",
|
||||
href: 'https://cojocarudavid.me',
|
||||
author: 'Cojocaru David',
|
||||
locale: 'en-US',
|
||||
|
||||
@@ -10,8 +10,7 @@ import { cn } from '@/lib/utils'
|
||||
import Posthog from '@/components/Posthog.astro'
|
||||
|
||||
const {
|
||||
isWide = false,
|
||||
canonicalUrl = SITE.href
|
||||
isWide = false
|
||||
} = Astro.props
|
||||
---
|
||||
|
||||
@@ -19,8 +18,16 @@ const {
|
||||
<html lang={SITE.locale}>
|
||||
<Head>
|
||||
<slot name="head" />
|
||||
<script src="https://analytics.ahrefs.com/analytics.js" data-key="+FHMgRP7/Duxaq5D0gZtJw" async></script>
|
||||
<script is:inline src="https://analytics.ahrefs.com/analytics.js" data-key="+FHMgRP7/Duxaq5D0gZtJw" async></script>
|
||||
<link rel="sitemap" href="/sitemap.xml" />
|
||||
|
||||
<!-- Preload critical resources -->
|
||||
<link rel="preload" href="/fonts/GeistVF.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="/fonts/GeistMonoVF.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<!-- DNS prefetch for external resources -->
|
||||
<link rel="dns-prefetch" href="//analytics.ahrefs.com" />
|
||||
|
||||
<script is:inline data-astro-rerun>
|
||||
(function() {
|
||||
try {
|
||||
@@ -43,7 +50,6 @@ const {
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<link rel="canonical" href={canonicalUrl} />
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS Feed" href="/rss.xml" />
|
||||
<Posthog />
|
||||
</Head>
|
||||
|
||||
@@ -9,21 +9,48 @@ const currentUrl = Astro.url;
|
||||
---
|
||||
|
||||
<Layout canonicalUrl={currentUrl}>
|
||||
<PageHead slot="head" title="404" />
|
||||
<PageHead
|
||||
slot="head"
|
||||
title="404 - Page Not Found"
|
||||
description="The page you're looking for couldn't be found. Return to the homepage to explore Cojocaru David's latest tech articles, projects, and programming insights."
|
||||
/>
|
||||
|
||||
<!-- Add 404 structured data -->
|
||||
<script type="application/ld+json" is:inline set:html={JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebPage",
|
||||
"name": "404 - Page Not Found",
|
||||
"description": "The requested page could not be found on this website.",
|
||||
"url": currentUrl.toString(),
|
||||
"mainEntity": {
|
||||
"@type": "Thing",
|
||||
"name": "404 Error",
|
||||
"description": "Page not found error"
|
||||
}
|
||||
})} />
|
||||
|
||||
<section
|
||||
class="flex w-full flex-col items-center justify-center gap-y-4 text-center"
|
||||
>
|
||||
<div class="max-w-md">
|
||||
<h1 class="mb-4 text-3xl font-medium">404: Page not found</h1>
|
||||
<p class="prose">Oops! The page you're looking for doesn't exist.</p>
|
||||
<h1 class="mb-4 text-3xl font-medium">404: Page Not Found</h1>
|
||||
<p class="prose mb-4">Oops! The page you're looking for doesn't exist. It might have been moved, deleted, or you entered the wrong URL.</p>
|
||||
<p class="prose text-sm text-muted-foreground">Looking for something specific? Try searching our blog or browse our latest articles.</p>
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<Link
|
||||
href="/"
|
||||
class={cn(buttonVariants({ variant: 'default' }), 'flex gap-x-1.5 group')}
|
||||
>
|
||||
<span class="transition-transform group-hover:-translate-x-1">←</span>
|
||||
Go to Home
|
||||
</Link>
|
||||
<Link
|
||||
href="/blog"
|
||||
class={cn(buttonVariants({ variant: 'outline' }), 'flex gap-x-1.5')}
|
||||
>
|
||||
Browse Blog
|
||||
</Link>
|
||||
</div>
|
||||
<Link
|
||||
href="/"
|
||||
class={cn(buttonVariants({ variant: 'outline' }), 'flex gap-x-1.5 group')}
|
||||
>
|
||||
<span class="transition-transform group-hover:-translate-x-1">←</span
|
||||
> Go to home page
|
||||
</Link>
|
||||
</section>
|
||||
</Layout>
|
||||
|
||||
@@ -41,10 +41,10 @@ const structuredData = {
|
||||
keywords: post.data.tags ? post.data.tags.join(', ') : '',
|
||||
description: post.data.description || '',
|
||||
}
|
||||
const currentUrl = Astro.url;
|
||||
const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
|
||||
---
|
||||
|
||||
<Layout canonicalUrl={currentUrl} isWide={true}>
|
||||
<Layout canonicalUrl={canonicalUrl} isWide={true}>
|
||||
<PostHead slot="head" post={post} />
|
||||
|
||||
<script
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
import type { APIRoute } from 'astro'
|
||||
import { SITE } from '@/consts'
|
||||
|
||||
const getRobotsTxt = (sitemapURL: URL) => `
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# Block access to admin or private directories (if any exist)
|
||||
Disallow: /api/
|
||||
Disallow: /_astro/
|
||||
Disallow: /temp/
|
||||
|
||||
# Crawl delay for better server performance
|
||||
Crawl-delay: 1
|
||||
|
||||
# Sitemap location
|
||||
Sitemap: ${sitemapURL.href}
|
||||
Sitemap: ${new URL('sitemap-index.xml', SITE.href).href}
|
||||
|
||||
# Additional information
|
||||
# Host: ${SITE.href}
|
||||
`
|
||||
|
||||
export const GET: APIRoute = ({ site }) => {
|
||||
|
||||
@@ -8,15 +8,33 @@ export async function GET(context: APIContext) {
|
||||
const posts = await getAllPosts()
|
||||
|
||||
return rss({
|
||||
title: SITE.title,
|
||||
title: `${SITE.title} - Tech Blog`,
|
||||
description: SITE.description,
|
||||
site: context.site ?? SITE.href,
|
||||
trailingSlash: false,
|
||||
items: posts.map((post) => ({
|
||||
title: post.data.title,
|
||||
description: post.data.description,
|
||||
pubDate: post.data.date,
|
||||
link: `/blog/${post.id}/`,
|
||||
categories: post.data.tags || [],
|
||||
author: post.data.authors ? post.data.authors.join(', ') : SITE.author,
|
||||
customData: `
|
||||
<language>${SITE.locale}</language>
|
||||
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
|
||||
${post.data.tags ? `<category>${post.data.tags.join(', ')}</category>` : ''}
|
||||
`.trim(),
|
||||
})),
|
||||
customData: `
|
||||
<language>${SITE.locale}</language>
|
||||
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
|
||||
<ttl>60</ttl>
|
||||
<image>
|
||||
<url>${new URL('/ogImage.png', SITE.href).toString()}</url>
|
||||
<title>${SITE.title}</title>
|
||||
<link>${SITE.href}</link>
|
||||
</image>
|
||||
`.trim(),
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error generating RSS feed:', error)
|
||||
|
||||
@@ -18,16 +18,22 @@ export async function GET(context: APIContext) {
|
||||
priority: '1.0'
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/projects`,
|
||||
url: `${baseUrl}/projects/`,
|
||||
lastmod: new Date().toISOString(),
|
||||
changefreq: 'weekly',
|
||||
priority: '0.8'
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/blog`,
|
||||
url: `${baseUrl}/blog/`,
|
||||
lastmod: posts.length > 0 ? posts[0].data.date.toISOString() : new Date().toISOString(),
|
||||
changefreq: 'daily',
|
||||
priority: '0.9'
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/tags/`,
|
||||
lastmod: new Date().toISOString(),
|
||||
changefreq: 'weekly',
|
||||
priority: '0.8'
|
||||
priority: '0.6'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -35,7 +41,7 @@ export async function GET(context: APIContext) {
|
||||
url: `${baseUrl}/blog/${post.id}/`,
|
||||
lastmod: post.data.date.toISOString(),
|
||||
changefreq: 'monthly',
|
||||
priority: '0.6'
|
||||
priority: '0.7'
|
||||
}))
|
||||
|
||||
const projectPosts = projects.map(project => ({
|
||||
@@ -45,7 +51,7 @@ export async function GET(context: APIContext) {
|
||||
priority: '0.6'
|
||||
}))
|
||||
|
||||
const tagUrls = Array.from(tags, ([tag]) => ({
|
||||
const tagUrls = Array.from(tags, ([tag, _]) => ({
|
||||
url: `${baseUrl}/tags/${tag}/`,
|
||||
lastmod: new Date().toISOString(),
|
||||
changefreq: 'weekly',
|
||||
|
||||
Reference in New Issue
Block a user