refactor: update site metadata structure

This commit is contained in:
enscribe 2025-03-22 17:21:30 -07:00
parent 71d1df3bd7
commit 931bf7277c
No known key found for this signature in database
GPG key ID: 9BBD5C4114E25322
15 changed files with 123 additions and 114 deletions

View file

@ -94,28 +94,30 @@ This is a list of the various technologies used to build this template:
Edit the `src/consts.ts` file to update your site's metadata, navigation links, and social links: Edit the `src/consts.ts` file to update your site's metadata, navigation links, and social links:
```typescript ```ts
export const SITE: Site = { export const SITE: Site = {
TITLE: 'astro-erudite', title: 'astro-erudite',
DESCRIPTION: description:
'astro-erudite is a opinionated, unstyled blogging template—built with Astro, Tailwind, and shadcn/ui.', 'astro-erudite is a opinionated, unstyled blogging template—built with Astro, Tailwind, and shadcn/ui.',
EMAIL: 'jason@enscribe.dev', href: 'https://astro-erudite.vercel.app',
NUM_POSTS_ON_HOMEPAGE: 2, featuredPostCount: 2,
SITEURL: 'https://astro-erudite.vercel.app', postsPerPage: 3,
} }
export const NAV_LINKS: Link[] = [ export const NAV_LINKS: SocialLink[] = [
{ href: '/blog', label: 'blog' }, {
{ href: '/authors', label: 'authors' }, href: '/blog',
{ href: '/about', label: 'about' }, label: 'blog',
{ href: '/tags', label: 'tags' }, },
// ...
] ]
export const SOCIAL_LINKS: Link[] = [ export const SOCIAL_LINKS: SocialLink[] = [
{ href: 'https://github.com/jktrn', label: 'GitHub' }, {
{ href: 'https://twitter.com/enscry', label: 'Twitter' }, href: 'https://github.com/jktrn',
{ href: 'jason@enscribe.dev', label: 'Email' }, label: 'GitHub',
{ href: '/rss.xml', label: 'RSS' }, },
// ...
] ]
``` ```
@ -185,8 +187,8 @@ The blog post schema is defined as follows:
| Field | Type (Zod) | Requirements | Required | | Field | Type (Zod) | Requirements | Required |
| ------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | ------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `title` | `string` | Must be ≤60 characters. | Yes | | `title` | `string` | Should be ≤60 characters. | Yes |
| `description` | `string` | Must be ≤155 characters. | Yes | | `description` | `string` | Should be ≤155 characters. | Yes |
| `date` | `coerce.date()` | Must be in `YYYY-MM-DD` format. | Yes | | `date` | `coerce.date()` | Must be in `YYYY-MM-DD` format. | Yes |
| `image` | `image()` | Should be exactly 1200px × 630px. | Optional | | `image` | `image()` | Should be exactly 1200px × 630px. | Optional |
| `tags` | `string[]` | Preferably use kebab-case for these. | Optional | | `tags` | `string[]` | Preferably use kebab-case for these. | Optional |

View file

@ -1,8 +1,8 @@
--- ---
import Link from '@/components/Link.astro' import Link from '@/components/Link.astro'
import AvatarComponent from '@/components/ui/avatar' import AvatarComponent from '@/components/ui/avatar'
import type { Link as SocialLink } from '@/consts'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import type { SocialLink } from '@/types'
import type { CollectionEntry } from 'astro:content' import type { CollectionEntry } from 'astro:content'
import SocialIcons from './SocialIcons.astro' import SocialIcons from './SocialIcons.astro'

View file

@ -30,7 +30,7 @@ const { title, description, image = '/static/twitter-card.png' } = Astro.props
<title>{title}</title> <title>{title}</title>
<meta name="title" content={title} /> <meta name="title" content={title} />
<meta name="description" content={description} /> <meta name="description" content={description} />
<meta name="author" content={SITE.TITLE} /> <meta name="author" content={SITE.title} />
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" /> <link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
@ -44,7 +44,7 @@ const { title, description, image = '/static/twitter-card.png' } = Astro.props
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content={Astro.url} /> <meta property="og:url" content={Astro.url} />
<meta property="og:site_name" content={SITE.TITLE} /> <meta property="og:site_name" content={SITE.title} />
<meta property="og:title" content={title} /> <meta property="og:title" content={title} />
<meta property="og:description" content={description} /> <meta property="og:description" content={description} />
<meta property="og:image" content={new URL(image, Astro.url)} /> <meta property="og:image" content={new URL(image, Astro.url)} />

View file

@ -19,7 +19,7 @@ import logo from '../../public/static/logo.svg'
class="hover:text-primary flex shrink-0 items-center gap-2 text-xl font-medium transition-colors duration-300" class="hover:text-primary flex shrink-0 items-center gap-2 text-xl font-medium transition-colors duration-300"
> >
<Image src={logo} alt="Logo" class="size-8" /> <Image src={logo} alt="Logo" class="size-8" />
{SITE.TITLE} {SITE.title}
</Link> </Link>
<div class="flex items-center gap-2 md:gap-4"> <div class="flex items-center gap-2 md:gap-4">
<nav class="hidden items-center gap-4 text-sm sm:gap-6 md:flex"> <nav class="hidden items-center gap-4 text-sm sm:gap-6 md:flex">

View file

@ -1,51 +1,37 @@
--- ---
import Link from '@/components/Link.astro' import Link from '@/components/Link.astro'
import { buttonVariants } from '@/components/ui/button' import { buttonVariants } from '@/components/ui/button'
import type { Link as SocialLink } from '@/consts' import { ICON_MAP } from '@/consts'
import { cn } from '@/lib/utils' import type { SocialLink } from '@/types'
import { Icon } from 'astro-icon/components' import { Icon } from 'astro-icon/components'
interface Props { interface Props {
links: SocialLink[] links: SocialLink[]
className?: string
} }
const { links, className } = Astro.props const { links } = Astro.props
const iconMap = {
Website: 'lucide:globe',
GitHub: 'lucide:github',
LinkedIn: 'lucide:linkedin',
Twitter: 'lucide:twitter',
Email: 'lucide:mail',
RSS: 'lucide:rss',
}
const getSocialLink = ({ href, label }: SocialLink) => ({
href: label === 'Email' ? `mailto:${href}` : href,
ariaLabel: label,
iconName:
iconMap[label as keyof typeof iconMap] || 'lucide:message-circle-question',
})
--- ---
<ul class={cn('flex flex-wrap gap-2', className)} role="list"> <ul class="flex flex-wrap gap-2" role="list">
{ {
links.map((link) => { links.map(({ href, label }) => (
const { href, ariaLabel, iconName } = getSocialLink(link) <li>
return ( <Link
<li> href={href}
<Link aria-label={label}
href={href} title={label}
aria-label={ariaLabel} class={buttonVariants({ variant: 'outline', size: 'icon' })}
title={ariaLabel} external
class={buttonVariants({ variant: 'outline', size: 'icon' })} >
external <Icon
> name={
<Icon name={iconName} class="size-4" /> ICON_MAP[label as keyof typeof ICON_MAP] ||
</Link> 'lucide:message-circle-question'
</li> }
) class="size-4"
}) />
</Link>
</li>
))
} }
</ul> </ul>

View file

@ -1,37 +1,53 @@
export type Site = { import type { IconMap, SocialLink, Site } from '@/types'
TITLE: string
DESCRIPTION: string
EMAIL: string
NUM_POSTS_ON_HOMEPAGE: number
POSTS_PER_PAGE: number
SITEURL: string
}
export type Link = {
href: string
label: string
}
export const SITE: Site = { export const SITE: Site = {
TITLE: 'astro-erudite', title: 'astro-erudite',
DESCRIPTION: description:
'astro-erudite is a opinionated, unstyled blogging template—built with Astro, Tailwind, and shadcn/ui.', 'astro-erudite is a opinionated, unstyled blogging template—built with Astro, Tailwind, and shadcn/ui.',
EMAIL: 'jason@enscribe.dev', href: 'https://astro-erudite.vercel.app',
NUM_POSTS_ON_HOMEPAGE: 2, featuredPostCount: 2,
POSTS_PER_PAGE: 3, postsPerPage: 3,
SITEURL: 'https://astro-erudite.vercel.app',
} }
export const NAV_LINKS: Link[] = [ export const NAV_LINKS: SocialLink[] = [
{ href: '/blog', label: 'blog' }, {
{ href: '/authors', label: 'authors' }, href: '/blog',
{ href: '/about', label: 'about' }, label: 'blog',
{ href: '/tags', label: 'tags' }, },
{
href: '/authors',
label: 'authors',
},
{
href: '/about',
label: 'about',
},
] ]
export const SOCIAL_LINKS: Link[] = [ export const SOCIAL_LINKS: SocialLink[] = [
{ href: 'https://github.com/jktrn', label: 'GitHub' }, {
{ href: 'https://twitter.com/enscry', label: 'Twitter' }, href: 'https://github.com/jktrn',
{ href: 'jason@enscribe.dev', label: 'Email' }, label: 'GitHub',
{ href: '/rss.xml', label: 'RSS' }, },
{
href: 'https://twitter.com/enscry',
label: 'Twitter',
},
{
href: 'mailto:jason@enscribe.dev',
label: 'Email',
},
{
href: '/rss.xml',
label: 'RSS',
},
] ]
export const ICON_MAP: IconMap = {
Website: 'lucide:globe',
GitHub: 'lucide:github',
LinkedIn: 'lucide:linkedin',
Twitter: 'lucide:twitter',
Email: 'lucide:mail',
RSS: 'lucide:rss',
}

View file

@ -5,18 +5,8 @@ const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }), loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: ({ image }) => schema: ({ image }) =>
z.object({ z.object({
title: z title: z.string(),
.string() description: z.string(),
.max(
60,
'Title should be 60 characters or less for optimal Open Graph display.',
),
description: z
.string()
.max(
155,
'Description should be 155 characters or less for optimal Open Graph display.',
),
date: z.coerce.date(), date: z.coerce.date(),
image: image().optional(), image: image().optional(),
tags: z.array(z.string()).optional(), tags: z.array(z.string()).optional(),

View file

@ -17,7 +17,7 @@ const { title, description, image } = Astro.props
<html lang="en"> <html lang="en">
<head> <head>
<Head <Head
title={`${title} | ${SITE.TITLE}`} title={`${title} | ${SITE.title}`}
description={description} description={description}
image={image} image={image}
/> />

View file

@ -8,7 +8,7 @@ import Layout from '@/layouts/Layout.astro'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
--- ---
<Layout title="404" description={SITE.DESCRIPTION}> <Layout title="404" description={SITE.description}>
<Container class="flex grow flex-col gap-y-6"> <Container class="flex grow flex-col gap-y-6">
<Breadcrumbs items={[{ label: '???', icon: 'lucide:circle-help' }]} /> <Breadcrumbs items={[{ label: '???', icon: 'lucide:circle-help' }]} />

View file

@ -10,7 +10,7 @@ import { getCollection } from 'astro:content'
const projects = await getCollection('projects') const projects = await getCollection('projects')
--- ---
<Layout title="About" description={SITE.DESCRIPTION}> <Layout title="About" description={SITE.description}>
<Container class="flex flex-col gap-y-6"> <Container class="flex flex-col gap-y-6">
<Breadcrumbs items={[{ label: 'About', icon: 'lucide:info' }]} /> <Breadcrumbs items={[{ label: 'About', icon: 'lucide:info' }]} />

View file

@ -16,7 +16,7 @@ export async function getStaticPaths({
const allPosts = await getCollection('blog', ({ data }) => !data.draft) const allPosts = await getCollection('blog', ({ data }) => !data.draft)
return paginate( return paginate(
allPosts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()), allPosts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()),
{ pageSize: SITE.POSTS_PER_PAGE }, { pageSize: SITE.postsPerPage },
) )
} }

View file

@ -10,10 +10,10 @@ import { getCollection } from 'astro:content'
const blog = (await getCollection('blog')) const blog = (await getCollection('blog'))
.filter((post) => !post.data.draft) .filter((post) => !post.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
.slice(0, SITE.NUM_POSTS_ON_HOMEPAGE) .slice(0, SITE.featuredPostCount)
--- ---
<Layout title="Home" description={SITE.DESCRIPTION}> <Layout title="Home" description={SITE.description}>
<Container class="flex flex-col gap-y-6"> <Container class="flex flex-col gap-y-6">
<section> <section>
<div class="rounded-lg border"> <div class="rounded-lg border">

View file

@ -9,9 +9,9 @@ export async function GET(context: APIContext) {
posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
return rss({ return rss({
title: SITE.TITLE, title: SITE.title,
description: SITE.DESCRIPTION, description: SITE.description,
site: context.site ?? SITE.SITEURL, site: context.site ?? SITE.href,
items: posts.map((post) => ({ items: posts.map((post) => ({
title: post.data.title, title: post.data.title,
description: post.data.description, description: post.data.description,

View file

@ -39,7 +39,7 @@
@font-face { @font-face {
font-family: 'Geist'; font-family: 'Geist';
src: url('/fonts/GeistVF.woff2') format('woff2-variations'); src: url('/fonts/GeistVF.woff2') format('woff2-variations');
font-weight: 100 500; font-weight: 100 900;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
@ -47,7 +47,7 @@
@font-face { @font-face {
font-family: 'Geist Mono'; font-family: 'Geist Mono';
src: url('/fonts/GeistMonoVF.woff2') format('woff2-variations'); src: url('/fonts/GeistMonoVF.woff2') format('woff2-variations');
font-weight: 100 600; font-weight: 100 900;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
@ -94,11 +94,10 @@
} }
html { html {
color-scheme: light; @apply bg-background text-foreground scheme-light;
@apply bg-background text-foreground;
&.dark { &.dark {
color-scheme: dark; @apply scheme-dark;
} }
::-webkit-scrollbar-corner { ::-webkit-scrollbar-corner {

16
src/types.ts Normal file
View file

@ -0,0 +1,16 @@
export type Site = {
title: string
description: string
href: string
featuredPostCount: number
postsPerPage: number
}
export type SocialLink = {
href: string
label: string
}
export type IconMap = {
[key: string]: string
}