diff --git a/package-lock.json b/package-lock.json index fc2d47fb..acce6d93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "@octokit/graphql-schema": "^14.39.0", "@tailwindcss/typography": "^0.5.9", "@types/parse-github-url": "^1.0.2", + "astro-og-canvas": "^0.2.1", "dotenv": "^16.3.1", "linkedom": "^0.16.1", "parse-github-url": "^1.0.2", @@ -2928,6 +2929,22 @@ "sharp": "^0.32.5" } }, + "node_modules/astro-og-canvas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/astro-og-canvas/-/astro-og-canvas-0.2.1.tgz", + "integrity": "sha512-FuUKUXOt9ZXSgjcjyy7+GHJHx7z9csHne6MTogBJ8oGvxG2mMBUsBCdO5xvBtM+TwNnT4tMpGe1rCph2BLsR5Q==", + "dev": true, + "dependencies": { + "canvaskit-wasm": "^0.37.0", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "astro": "^3.0.0" + } + }, "node_modules/astro/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3330,6 +3347,12 @@ } ] }, + "node_modules/canvaskit-wasm": { + "version": "0.37.2", + "resolved": "https://registry.npmjs.org/canvaskit-wasm/-/canvaskit-wasm-0.37.2.tgz", + "integrity": "sha512-212imazRF98gLOTiU4JAXM7xDvaknI7jaPtAg4ETXGW5rLQs6pomgIvVPUSfoKnQVTdGgzj+B4e+/u0Da20aGg==", + "dev": true + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -14060,6 +14083,16 @@ } } }, + "astro-og-canvas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/astro-og-canvas/-/astro-og-canvas-0.2.1.tgz", + "integrity": "sha512-FuUKUXOt9ZXSgjcjyy7+GHJHx7z9csHne6MTogBJ8oGvxG2mMBUsBCdO5xvBtM+TwNnT4tMpGe1rCph2BLsR5Q==", + "dev": true, + "requires": { + "canvaskit-wasm": "^0.37.0", + "entities": "^4.4.0" + } + }, "async-listen": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-3.0.0.tgz", @@ -14256,6 +14289,12 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001525.tgz", "integrity": "sha512-/3z+wB4icFt3r0USMwxujAqRvaD/B7rvGTsKhbhSQErVrJvkZCLhgNLJxU8MevahQVH6hCU9FsHdNUFbiwmE7Q==" }, + "canvaskit-wasm": { + "version": "0.37.2", + "resolved": "https://registry.npmjs.org/canvaskit-wasm/-/canvaskit-wasm-0.37.2.tgz", + "integrity": "sha512-212imazRF98gLOTiU4JAXM7xDvaknI7jaPtAg4ETXGW5rLQs6pomgIvVPUSfoKnQVTdGgzj+B4e+/u0Da20aGg==", + "dev": true + }, "ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", diff --git a/package.json b/package.json index fb73e86b..083c1fa6 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@octokit/graphql-schema": "^14.39.0", "@tailwindcss/typography": "^0.5.9", "@types/parse-github-url": "^1.0.2", + "astro-og-canvas": "^0.2.1", "dotenv": "^16.3.1", "linkedom": "^0.16.1", "parse-github-url": "^1.0.2", diff --git a/public/images/404.png b/public/images/404.png new file mode 100644 index 00000000..80e3eddf Binary files /dev/null and b/public/images/404.png differ diff --git a/public/images/about-author.png b/public/images/about-author.png new file mode 100644 index 00000000..955eb050 Binary files /dev/null and b/public/images/about-author.png differ diff --git a/public/images/articles.png b/public/images/articles.png new file mode 100644 index 00000000..1d606641 Binary files /dev/null and b/public/images/articles.png differ diff --git a/public/images/cookie-policy.png b/public/images/cookie-policy.png new file mode 100644 index 00000000..021f212f Binary files /dev/null and b/public/images/cookie-policy.png differ diff --git a/public/images/guide.png b/public/images/guide.png new file mode 100644 index 00000000..b1984384 Binary files /dev/null and b/public/images/guide.png differ diff --git a/public/images/index.png b/public/images/index.png new file mode 100644 index 00000000..80e3eddf Binary files /dev/null and b/public/images/index.png differ diff --git a/public/images/open-sourcerers.png b/public/images/open-sourcerers.png new file mode 100644 index 00000000..cf762704 Binary files /dev/null and b/public/images/open-sourcerers.png differ diff --git a/public/images/showcase.png b/public/images/showcase.png new file mode 100644 index 00000000..76b7f269 Binary files /dev/null and b/public/images/showcase.png differ diff --git a/src/components/HeadSEO.astro b/src/components/HeadSEO.astro new file mode 100644 index 00000000..2d1a8b86 --- /dev/null +++ b/src/components/HeadSEO.astro @@ -0,0 +1,36 @@ +--- +import { getOpenGraphImageURL } from "../util/getOpenGraphImageURL"; + +const { canonicalURL, openGraphData } = Astro.props; + +let canonicalImageSrc; + +if (openGraphData.staticImage) { + const imagePath = canonicalURL.pathname.replaceAll(/\//g, '') + canonicalImageSrc = new URL('images/' + (imagePath ? imagePath : 'index') + '.png', Astro.site); +} else { + const ogImageUrl = getOpenGraphImageURL(canonicalURL.pathname, !!Astro.params.fallback); + const imageSrc = ogImageUrl ? ogImageUrl : '/images/index.png'; + canonicalImageSrc = new URL(imageSrc, Astro.site); +} + +--- + + + + + + + + + + + + + + + + + + + diff --git a/src/content/config.ts b/src/content/config.ts index 7933b505..e40cec76 100644 --- a/src/content/config.ts +++ b/src/content/config.ts @@ -1,5 +1,12 @@ import { z, defineCollection } from 'astro:content'; +const OpenGraphSchemaDataSchema = z.object({ + description: z.string(), + staticImage: z.boolean(), + title: z.string(), + type: z.string(), +}) + const structuredDataSchema = z.object({ article: z.object({ headline: z.string().optional(), @@ -87,6 +94,7 @@ export const collections = { 'showcase': showcaseCollection }; +export type OpenGraphData = z.infer; export type Showcase = z.infer; export type ShowcaseLink = z.infer; export type ShowcaseUnknownLink = z.infer; diff --git a/src/docs-logo.png b/src/docs-logo.png new file mode 100644 index 00000000..bb087cff Binary files /dev/null and b/src/docs-logo.png differ diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index b5caad08..b65f8fc7 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -4,7 +4,8 @@ import Footer from '../components/Footer.astro'; import '../global.css'; import ProgressScroll from '../components/ProgressScroll.astro'; import SkipLink from '../components/SkipLink.astro'; -import type { StructuredData } from '../content/config'; +import type { OpenGraphData, StructuredData } from '../content/config'; +import HeadSEO from '../components/HeadSEO.astro'; import '@docsearch/css'; @@ -14,18 +15,23 @@ export interface Props { title: string; progressScroll?: boolean; structuredData: StructuredData; + openGraphData: OpenGraphData; } -const { home = false, title, description, progressScroll = false, structuredData } = Astro.props; +// Ensures the canonicalURL always has a trailing slash. +const canonicalURL = new URL(Astro.url.pathname.replace(/([^/])$/, '$1/'), Astro.site); + +const { home = false, title, description, progressScroll = false, structuredData, openGraphData } = Astro.props; let articleSchema; + if (structuredData?.article) { articleSchema = JSON.stringify({ "@context": "https://schema.org/", "@type": "Article", "headline": structuredData?.article?.headline, "description": structuredData?.article?.description, - "image": "https://openresource.dev/og-image.png", + "image": "https://openresource.dev/images/open-graph" + canonicalURL.pathname.slice(0, -1) + ".png", "author": { "@type": "Person", "name": "Julien Déramond", @@ -76,6 +82,7 @@ if (structuredData?.article) { Open {re}Source - {title} + { articleSchema && } diff --git a/src/pages/404.astro b/src/pages/404.astro index c87d30e9..09afe223 100644 --- a/src/pages/404.astro +++ b/src/pages/404.astro @@ -1,4 +1,6 @@ --- +export const prerender = true + import Layout from '../layouts/Layout.astro'; const { @@ -6,7 +8,7 @@ const { description = 'Join Open Source, learn to create, manage, and contribute to projects with Open {re}Source. Make a difference today by sharing and collaborating.' } = Astro.props; --- - +
diff --git a/src/pages/about-author/index.astro b/src/pages/about-author/index.astro index 0810e7fb..72fd35cb 100644 --- a/src/pages/about-author/index.astro +++ b/src/pages/about-author/index.astro @@ -9,7 +9,7 @@ const { description = 'Find some information about Julien Déramond, the author of Open {re}Source.' } = Astro.props; --- - +
diff --git a/src/pages/articles/[...slug].astro b/src/pages/articles/[...slug].astro index ffc73aa0..582fb37b 100644 --- a/src/pages/articles/[...slug].astro +++ b/src/pages/articles/[...slug].astro @@ -27,7 +27,7 @@ entry.data.structuredData = { const { Content } = await entry.render(); --- - +
diff --git a/src/pages/articles/index.astro b/src/pages/articles/index.astro index ea7321eb..0213939e 100644 --- a/src/pages/articles/index.astro +++ b/src/pages/articles/index.astro @@ -12,7 +12,7 @@ const { const allArticles = await getCollection('articles'); --- - +
diff --git a/src/pages/guide/[...slug].astro b/src/pages/guide/[...slug].astro index 91607645..2c0ba8f1 100644 --- a/src/pages/guide/[...slug].astro +++ b/src/pages/guide/[...slug].astro @@ -45,10 +45,11 @@ export async function getStaticPaths() { } const { entry, isIndex, orderedFilteredChapters, module } = Astro.props; + const { Content } = await entry.render(); --- { isIndex && ( - +