diff options
author | Pauline <git@ethanlibs.co> | 2024-01-22 04:27:34 +0100 |
---|---|---|
committer | Pauline <git@ethanlibs.co> | 2024-01-22 04:27:48 +0100 |
commit | f0e73cf90005709e20b2f16f63d67d6802af3332 (patch) | |
tree | 1fb33d1bf14ada662a9cbdea1728c3f031c06ff0 /apps/website | |
parent | e1f6521a78f5c2a873199fd8cded0bc58ac3aa6e (diff) | |
download | Nexus-f0e73cf90005709e20b2f16f63d67d6802af3332.tar.gz Nexus-f0e73cf90005709e20b2f16f63d67d6802af3332.tar.bz2 Nexus-f0e73cf90005709e20b2f16f63d67d6802af3332.zip |
feat(layout): add seo metadata to all pages
Diffstat (limited to 'apps/website')
24 files changed, 193 insertions, 113 deletions
diff --git a/apps/website/src/components/shared/BaseHead.astro b/apps/website/src/components/shared/BaseHead.astro index bfe8f78..39e98a7 100644 --- a/apps/website/src/components/shared/BaseHead.astro +++ b/apps/website/src/components/shared/BaseHead.astro @@ -1,12 +1,57 @@ --- +import smartypants from 'smartypants'; +import siteInfo from '../../data/site-info'; +import SEO from './SEO.astro'; +import Favicon from '/media/polyfrost/minimal_bg.svg?url'; + export type Props = { - siteName: string; title?: string; - description: string; - image: { src: string; alt: string }; + description?: string; + image?: { src: string; alt: string }; canonicalURL?: URL | null; pageType?: 'website' | 'article'; }; const twitterHandle = 'polyfrost'; + +const { + description = siteInfo.description, + image = siteInfo.image, + canonicalURL = new URL(Astro.request.url, Astro.site), + pageType = 'website', +} = Astro.props; + +const title = [Astro.props.title, siteInfo.name].filter(Boolean).join(' | '); +const resolvedImage = { + src: new URL(image.src, Astro.site).toString(), + alt: image.alt, +}; --- + +<!-- High Priority Global Metadata --> +<meta charset="utf-8" /> +<meta name="viewport" content="width=device-width" /> +<title set:html={smartypants(title, 1)} /> +<meta name="generator" content={Astro.generator} /> + +<!-- Font Preloads --> +<link rel="preconnect" href="https://fonts.googleapis.com"/> +<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/> +<link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet"/> +<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital@0;1&display=swap" rel="stylesheet"/> + +<!-- Low Priority Global Metadata --> +<link rel="icon" type="image/svg+xml" href={Favicon}/> +<link rel="sitemap" href="/sitemap-index.xml" /> +<link rel="mask-icon" href="/media/polyfrost/minimal_bg.svg" /> +<link rel="alternate" type="application/rss+xml" href="/rss.xml" title="RSS" /> + +<SEO + name={siteInfo.name} + title={title} + description={description} + image={resolvedImage} + twitter={{ handle: twitterHandle }} + og={{ type: pageType }} + canonicalURL={canonicalURL} +/> diff --git a/apps/website/src/components/shared/SEO.astro b/apps/website/src/components/shared/SEO.astro index c3b8c52..cd44da4 100644 --- a/apps/website/src/components/shared/SEO.astro +++ b/apps/website/src/components/shared/SEO.astro @@ -1,20 +1,23 @@ --- -import smartypants from 'smartypants'; +export type Image = { + src: string; + alt: string; +}; -type SEOMetadata = { - name?: string; +export type SEOMetadata = { + name: string; title: string; description: string; - image?: { src: string; alt: string }; - canonicalURL?: URL | null; + image: Image; + canonicalURL?: URL | string | null; locale?: string; }; -type OpenGraph = Partial<SEOMetadata> & { +export type OpenGraph = Partial<SEOMetadata> & { type?: string; }; -type Twitter = Partial<SEOMetadata> & { +export type Twitter = Partial<SEOMetadata> & { handle?: string; card?: 'summary' | 'summary_large_image'; }; @@ -26,64 +29,62 @@ export type Props = SEOMetadata & { const { name, + title, description, image, locale = 'en', canonicalURL = new URL(Astro.url.pathname, Astro.site), - og: _og = {}, - twitter: _twitter = {}, } = Astro.props; -const title = [Astro.props.title, name].filter(Boolean).join(' | '); -const og: OpenGraph = { name, title, description, canonicalURL, image, locale, type: 'website', ..._og }; -const twitter: Twitter = { name, title, description, canonicalURL, image, locale, card: 'summary_large_image', ..._twitter }; -const ensureSlash = (url: string | URL) => `${url.toString().replace(/\/$/, '')}/`; ---- +const og = { + name, + title, + description, + canonicalURL, + image, + locale, + type: 'website', + ...Astro.props.og, +} satisfies OpenGraph; -<!-- Global Metadata --> -<meta charset="utf-8" /> -<meta name="generator" content={Astro.generator} /> -<meta name="viewport" content="width=device-width" /> -<meta name="theme-color" content="#d2e1f9" /> -<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> -<link rel="mask-icon" href="/favicon.svg" color="#d2e1f9" /> -<link rel="sitemap" href="/sitemap-index.xml" /> -<link rel="alternate" type="application/rss+xml" href="/rss.xml" title="RSS" /> +const twitter = { + name, + title, + description, + canonicalURL, + image, + locale, + card: 'summary_large_image', + ...Astro.props.twitter, +} satisfies Twitter; -<title set:html={smartypants(title, 1)} /> +function formatCanonicalURL(url: string | URL) { + const path = url.toString(); + const hasQueryParams = path.includes('?'); + if (hasQueryParams) + path.replace(/\/?$/, ''); + return path.replace(/\/?$/, hasQueryParams ? '' : ''); +} +--- <!-- Page Metadata --> -<meta name="generator" content={Astro.generator} /> -{canonicalURL && <link rel="canonical" href={ensureSlash(canonicalURL)} />} -<title>{title}</title> +{canonicalURL && <link rel="canonical" href={formatCanonicalURL(canonicalURL)} />} <meta name="description" content={description} /> <!-- OpenGraph Tags --> <meta property="og:title" content={og.title} /> <meta property="og:type" content={og.type} /> -{og.canonicalURL && <meta property="og:url" content={ensureSlash(og.canonicalURL)} />} +{og.canonicalURL && <meta property="og:url" content={formatCanonicalURL(og.canonicalURL)} />} <meta property="og:locale" content={og.locale} /> <meta property="og:description" content={og.description} /> <meta property="og:site_name" content={og.name} /> -{ - og.image && ( - <> - <meta property="og:image" content={og.image.src} /> - <meta property="og:image:alt" content={og.image.alt} /> - </> - ) -} +{og.image && <meta property="og:image" content={og.image.src} />} +{og.image && <meta property="og:image:alt" content={og.image.alt} />} <!-- Twitter Tags --> {twitter.card && <meta name="twitter:card" content={twitter.card} />} -{twitter.handle && <meta name="twitter:site" content={twitter.handle} />} +{twitter.handle && <meta name="twitte:site" content={twitter.handle} />} <meta name="twitter:title" content={twitter.title} /> <meta name="twitter:description" content={twitter.description} /> -{ - twitter.image && ( - <> - <meta name="twitter:image" content={twitter.image.src} /> - <meta name="twitter:image:alt" content={twitter.image.alt} /> - </> - ) -} +{twitter.image && <meta name="twitter:image" content={twitter.image.src} />} +{twitter.image && <meta name="twitter:image:alt" content={twitter.image.alt} />} diff --git a/apps/website/src/content/blog/first.md b/apps/website/src/content/blog/first.md index 3066715..2b5f64a 100644 --- a/apps/website/src/content/blog/first.md +++ b/apps/website/src/content/blog/first.md @@ -1,7 +1,7 @@ --- title: 'First post' description: 'Lorem ipsum dolor sit amet' -pubDate: 'Jul 08 2022' +publishDate: 'Jul 08 2022' heroImage: '/blog-placeholder-3.jpg' --- diff --git a/apps/website/src/content/config.ts b/apps/website/src/content/config.ts index 8d68c0e..6f6cde9 100644 --- a/apps/website/src/content/config.ts +++ b/apps/website/src/content/config.ts @@ -1,12 +1,15 @@ -import { defineCollection, z } from 'astro:content'; +import { defineCollection } from 'astro:content'; +import { z } from 'zod'; const blog = defineCollection({ schema: z.object({ title: z.string(), description: z.string(), - pubDate: z.coerce.date(), - updatedDate: z.coerce.date().optional(), - heroImage: z.string().optional(), + publishDate: z.string().or(z.date()).transform(val => new Date(val)), + updatedDate: z.string().or(z.date()).transform(val => new Date(val)).optional(), + socialImage: z.string().optional(), + coverImage: z.string().optional(), + lang: z.enum(['en']).default('en'), }), }); diff --git a/apps/website/src/data/site-info.ts b/apps/website/src/data/site-info.ts new file mode 100644 index 0000000..cb7784f --- /dev/null +++ b/apps/website/src/data/site-info.ts @@ -0,0 +1,22 @@ +export interface SiteInfo { + name: string; + title: string; + description: string; + image: { + src: string; + alt: string; + }; +}; + +// TODO: move config.ts to here so we can use astro components inside config +const siteInfo: SiteInfo = { + name: 'Polyfrost', + title: 'Polyfrost', + description: 'Building beautiful mods and tools, byte by byte', + image: { + src: '/media/polyfrost/minimal.svg', + alt: 'Polyfrost Logo', + }, +}; + +export default siteInfo; diff --git a/apps/website/src/layouts/BlogPost.astro b/apps/website/src/layouts/BlogPost.astro index 188aae1..17ff74d 100644 --- a/apps/website/src/layouts/BlogPost.astro +++ b/apps/website/src/layouts/BlogPost.astro @@ -6,18 +6,18 @@ import Layout from './Layout.astro'; type Props = CollectionEntry<'blog'>['data']; -const { title, description, pubDate, updatedDate, heroImage } = Astro.props; +const { title, description, publishDate, updatedDate, coverImage } = Astro.props; --- <Layout title={title} description={description}> <article> <div class="hero-image"> - {heroImage && <img width={1020} height={510} src={heroImage} alt="Hero Image"/>} + {coverImage && <img width={1020} height={510} src={coverImage} alt="Hero Image"/>} </div> <div class="prose"> <div class="title"> <div class="date"> - <FormattedDate date={pubDate}/> + <FormattedDate date={publishDate}/> { updatedDate && ( <div class="last-updated-on"> diff --git a/apps/website/src/layouts/Layout.astro b/apps/website/src/layouts/Layout.astro index f916ee1..b1cde5c 100644 --- a/apps/website/src/layouts/Layout.astro +++ b/apps/website/src/layouts/Layout.astro @@ -1,37 +1,18 @@ --- import Footer from '@components/base/Footer.astro'; -import Favicon from '/media/polyfrost/minimal_bg.svg?url'; import Navbar from '../components/base/navbar/Navbar.astro'; +import BaseHead, { type Props as HeadProps } from '../components/shared/BaseHead.astro'; import '../styles/global.css'; -interface Props { - title?: string; - description?: string; - favicon?: string; -} +export type Props = HeadProps; -const { - title = 'Polyfrost', - description = 'Official website for Polyfrost.', - favicon = Favicon, -} = Astro.props; +const { ...head } = Astro.props; --- <!doctype html> <html lang="en"> <head> - <meta charset="UTF-8"/> - <meta name="description" content={description}/> - <meta name="viewport" content="width=device-width"/> - <link rel="icon" type="image/svg+xml" href={favicon}/> - <meta name="generator" content={Astro.generator}/> - - <link rel="preconnect" href="https://fonts.googleapis.com"/> - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/> - <link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet"/> - <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital@0;1&display=swap" rel="stylesheet"/> - - <title>{title}</title> + <BaseHead {...head} /> </head> <body class="bg-gray-50 overflow-x-hidden"> diff --git a/apps/website/src/pages/about.astro b/apps/website/src/pages/about.astro index ac51204..5f85a7d 100644 --- a/apps/website/src/pages/about.astro +++ b/apps/website/src/pages/about.astro @@ -7,7 +7,7 @@ import Layout from '@layouts/Layout.astro'; --- -<Layout> +<Layout title="About us" description="Learn about the team!"> <Section class="flex-col justify-center items-center h-screen max-h-4/5-screen md:max-h-[600px] md:min-h-[400px]"> <div class="max-w-[600px] flex flex-col text-center justify-center items-center gap-y-2"> <Header align="center" size="xxl" class="text-navy-peony">Our journey</Header> diff --git a/apps/website/src/pages/blog/index.astro b/apps/website/src/pages/blog/index.astro index 4bccad4..8603764 100644 --- a/apps/website/src/pages/blog/index.astro +++ b/apps/website/src/pages/blog/index.astro @@ -4,9 +4,7 @@ import { getCollection } from 'astro:content'; import FormattedDate from '../../components/base/FormattedDate.astro'; import Layout from '../../layouts/Layout.astro'; -const posts = (await getCollection('blog')).sort( - (a, b) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf(), -); +const posts = await getCollection('blog'); --- <Layout title="Polyfrost Blog" description="Recieve Polyfrost updates here"> @@ -16,10 +14,10 @@ const posts = (await getCollection('blog')).sort( posts.map(post => ( <li> <a href={`blog/${post.slug}/`}> - <img width={720} height={360} src={post.data.heroImage} alt=""/> + <img width={720} height={360} src={post.data.coverImage} alt=""/> <h4 class="title">{post.data.title}</h4> <p class="date"> - <FormattedDate date={post.data.pubDate}/> + <FormattedDate date={post.data.publishDate}/> </p> </a> </li> diff --git a/apps/website/src/pages/branding.astro b/apps/website/src/pages/branding.astro index e367f3b..4b41edd 100644 --- a/apps/website/src/pages/branding.astro +++ b/apps/website/src/pages/branding.astro @@ -12,7 +12,7 @@ function formatModName(name: string): string { } --- -<Layout> +<Layout title="Polyfrost Branding" description="Read about our branding policy"> <Section class="justify-center items-start pt-32"> <div class="flex flex-col justify-start items-start w-full text-navy-peony"> <Header> diff --git a/apps/website/src/pages/contact.astro b/apps/website/src/pages/contact.astro index aac214d..53a6a81 100644 --- a/apps/website/src/pages/contact.astro +++ b/apps/website/src/pages/contact.astro @@ -8,7 +8,7 @@ import Layout from '@layouts/Layout.astro'; --- -<Layout> +<Layout title="Contact us" description="Learn how to reach out to the Polyfrost team and community"> <Section wrapperClass="min-h-screen" class="my-40 md:my-40 xl:my-20 2xl:my-20 justify-center items-center flex-col"> <Header size="xl" class="text-navy-peony text-center">Feeling social? Come chat with us</Header> diff --git a/apps/website/src/pages/index.astro b/apps/website/src/pages/index.astro index 14e59de..c2835ac 100644 --- a/apps/website/src/pages/index.astro +++ b/apps/website/src/pages/index.astro @@ -8,7 +8,7 @@ import Icon from '@components/icons/Icon.astro'; import configConst from '@config'; import Layout from '@layouts/Layout.astro'; --- -<Layout> +<Layout title="Main Page"> <Section wrapperClass="min-h-screen" class="relative isolate px-6 lg:px-8"> <div class="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80 opacity-50"> <svg diff --git a/apps/website/src/pages/legal/ip.astro b/apps/website/src/pages/legal/ip.astro index 13cfda5..f0192d1 100644 --- a/apps/website/src/pages/legal/ip.astro +++ b/apps/website/src/pages/legal/ip.astro @@ -10,7 +10,7 @@ This will be updated in the future, if necessary. `.trim(); --- -<Layout> +<Layout title="Intellectual Property Policy"> <Section hFull class="justify-center items-start pt-32"> <div class="flex flex-col justify-start items-start w-full text-navy-peony"> <Header> diff --git a/apps/website/src/pages/legal/privacy.astro b/apps/website/src/pages/legal/privacy.astro index d3940f2..61fbdff 100644 --- a/apps/website/src/pages/legal/privacy.astro +++ b/apps/website/src/pages/legal/privacy.astro @@ -10,7 +10,7 @@ This will be updated in the future, if necessary. `.trim(); --- -<Layout> +<Layout title="Privacy Policy"> <Section hFull class="justify-center items-start pt-32"> <div class="flex flex-col justify-start items-start w-full text-navy-peony"> <Header> diff --git a/apps/website/src/pages/legal/security.astro b/apps/website/src/pages/legal/security.astro index a80d3f9..0bbf50e 100644 --- a/apps/website/src/pages/legal/security.astro +++ b/apps/website/src/pages/legal/security.astro @@ -10,7 +10,7 @@ This will be updated in the future, if necessary. `.trim(); --- -<Layout> +<Layout title="Security Notice"> <Section hFull class="justify-center items-start pt-32"> <div class="flex flex-col justify-start items-start w-full text-navy-peony"> <Header> diff --git a/apps/website/src/pages/legal/terms.astro b/apps/website/src/pages/legal/terms.astro index 6ed2009..e894d76 100644 --- a/apps/website/src/pages/legal/terms.astro +++ b/apps/website/src/pages/legal/terms.astro @@ -10,7 +10,7 @@ This will be updated in the future, if necessary. `.trim(); --- -<Layout> +<Layout title="Terms of Service"> <Section hFull class="justify-center items-start pt-32"> <div class="flex flex-col justify-start items-start w-full text-navy-peony"> <Header> diff --git a/apps/website/src/pages/mods.astro b/apps/website/src/pages/mods.astro index ae1002c..adde3d7 100644 --- a/apps/website/src/pages/mods.astro +++ b/apps/website/src/pages/mods.astro @@ -14,7 +14,7 @@ const modrinthType = configConst.socials.modrinth.type; const modrinthUrl = `https://modrinth.com/${modrinthType}/${modrinthId}`; --- -<Layout> +<Layout title="Mods" description="Learn about our collection of over 16 mods!" > <Section maxWidth="1920px" wFull={true} wrapperClass="h-3/5" class="h-full mt-32 md:mt-28 flex flex-col justify-center items-center max-xl:px-0"> <div class="flex flex-col justify-center items-center max-w-full overflow-hidden"> <div class="flex flex-col justify-between items-center overflow-hidden h-auto lg:h-[290px] max-w-[1920px] relative"> diff --git a/apps/website/src/pages/oss.astro b/apps/website/src/pages/oss.astro index a54d929..b7b2715 100644 --- a/apps/website/src/pages/oss.astro +++ b/apps/website/src/pages/oss.astro @@ -39,7 +39,7 @@ const rightCodeBlock = leftCodeBlock; --- -<Layout> +<Layout title="Open Source" description="Our unwavering commitment to open source"> <Section maxWidth="1920px" wFull class="flex-row justify-center items-center h-screen md:min-h-[600px] relative"> <div class="codeblock_container -left-12"> <Code lang="java" theme="github-light" code={leftCodeBlock}></Code> diff --git a/apps/website/src/pages/projects/oneconfig/download.astro b/apps/website/src/pages/projects/oneconfig/download.astro index b8aee51..07279ba 100644 --- a/apps/website/src/pages/projects/oneconfig/download.astro +++ b/apps/website/src/pages/projects/oneconfig/download.astro @@ -7,7 +7,7 @@ import configConst from '@config'; import Layout from '@layouts/Layout.astro'; --- -<Layout> +<Layout title="Download OneConfig" description="Download OneConfig, our configuration library for Minecraft"> <Section wrapperClass="mt-36 -mb-28"> <div class="text-navy-peony flex flex-col gap-y-2"> diff --git a/apps/website/src/pages/projects/oneconfig/index.astro b/apps/website/src/pages/projects/oneconfig/index.astro index e8283f3..ed018cd 100644 --- a/apps/website/src/pages/projects/oneconfig/index.astro +++ b/apps/website/src/pages/projects/oneconfig/index.astro @@ -13,7 +13,7 @@ import { Code } from 'astro:components'; --- -<Layout> +<Layout title="OneConfig" description="Simple universal Minecraft modding configuration."> <Section class="flex-col justify-center items-center h-screen md:min-h-[600px]"> <div class="flex flex-col justify-center items-center gap-y-4"> <Logo size={56} logo="oneconfig.minimal"/> diff --git a/apps/website/src/pages/rss.xml.js b/apps/website/src/pages/rss.xml.js deleted file mode 100644 index fe05755..0000000 --- a/apps/website/src/pages/rss.xml.js +++ /dev/null @@ -1,16 +0,0 @@ -import rss from '@astrojs/rss'; -import { getCollection } from 'astro:content'; - -export async function GET(context) { - const posts = await getCollection('blog'); - - return rss({ - title: 'Polyfrost Blog', - description: 'Recieve Polyfrost updates here', - site: context.site, - items: posts.map(post => ({ - ...post.data, - link: `/blog/${post.slug}/`, - })), - }); -} diff --git a/apps/website/src/pages/rss.xml.ts b/apps/website/src/pages/rss.xml.ts new file mode 100644 index 0000000..db8cfff --- /dev/null +++ b/apps/website/src/pages/rss.xml.ts @@ -0,0 +1,30 @@ +import rss from '@astrojs/rss'; +import { getCollection } from 'astro:content'; +import type { APIRoute } from 'astro'; + +function sortPosts(a: { data: { publishDate: Date } }, b: { data: { publishDate: Date } }) { + return Number(b.data.publishDate) - Number(a.data.publishDate); +} + +function formatDate(date: Date) { + date.setUTCHours(0); + return date; +} + +export const GET: APIRoute = async (context) => { + const unsortedPosts = [...(await getCollection('blog'))]; + const posts = unsortedPosts.sort((a, b) => sortPosts(a, b)); + + return rss({ + title: 'Polyfrost Blog', + description: 'Recieve Polyfrost updates here', + site: context.site!.href, + items: posts.map(post => ({ + ...post.data, + title: post.data.title, + description: post.data.description, + pubDate: formatDate(post.data.publishDate), + link: `/blog/${post.slug}/`, + })), + }); +}; diff --git a/apps/website/src/process-env.ts b/apps/website/src/process-env.ts new file mode 100644 index 0000000..5106c6c --- /dev/null +++ b/apps/website/src/process-env.ts @@ -0,0 +1,14 @@ +import process from 'node:process'; +import { z } from 'zod'; + +const schema = z.object({}); + +export function env() { + const result = schema.safeParse(process.env); + if (!result.success) { + throw new Error(`Missing environment variables: ${result.error.issues + .map(issue => issue.path.join('.')).join(', ')}`); + } + + return result.data; +} diff --git a/apps/website/tsconfig.json b/apps/website/tsconfig.json index 139b7e0..1f976ee 100644 --- a/apps/website/tsconfig.json +++ b/apps/website/tsconfig.json @@ -2,6 +2,7 @@ "extends": "astro/tsconfigs/strict", "compilerOptions": { "composite": false, + "jsx": "preserve", "baseUrl": ".", "paths": { "@components/*": ["src/components/*"], @@ -12,5 +13,6 @@ "@config": ["config.ts"], "@styles/*": ["src/styles/*"] } - } + }, + "exclude": ["node_modules", "dist", ".vercel", "public"] } |