Skip to content

Commit

Permalink
feat(seo): json-ld, siblings article, json-ld, time tag
Browse files Browse the repository at this point in the history
  • Loading branch information
gregberge committed Dec 28, 2023
1 parent 6c6e916 commit 5bee326
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 44 deletions.
124 changes: 105 additions & 19 deletions app/blog/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { Metadata, ResolvingMetadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import * as React from "react";

import { Container } from "@/components/Container";
import {
PostCard,
PostCardAuthor,
PostCardBody,
PostCardDate,
PostCardFooter,
PostCardImage,
PostCardTag,
PostCardTitle,
} from "@/components/PostCard";
import { getArticleBySlug, getArticles, getDocMdxSource } from "@/lib/blog-api";

type Props = {
Expand Down Expand Up @@ -48,33 +59,108 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
};
}

async function Siblings({ slug }: { slug: string }) {
const articles = await getArticles();
const sideArticles = articles
.filter((article) => article.slug !== slug)
.map((value) => ({ value, sort: Math.random() }))
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => value)
.slice(0, 2);
return (
<section>
<h3 className="mb-8 text-2xl font-semibold text">Read also</h3>
<div className="grid grid-cols-2 gap-x-16 gap-y-20">
{sideArticles.map((article) => {
return (
<Link
key={article.slug}
href={`/blog/${article.slug}`}
className="contents"
>
<PostCard className="col-span-2 md:col-span-1">
<PostCardImage
width={article.image.width}
height={article.image.height}
src={article.image.src}
alt={article.imageAlt}
/>
<PostCardBody>
{article.category && (
<PostCardTag>{article.category}</PostCardTag>
)}
<PostCardTitle>{article.title}</PostCardTitle>
<PostCardFooter>
<PostCardAuthor>{article.author}</PostCardAuthor>
<div className="text-xs text-mauve-10">|</div>
<PostCardDate date={article.date} />
</PostCardFooter>
</PostCardBody>
</PostCard>
</Link>
);
})}
</div>
</section>
);
}

const dateFormatter = new Intl.DateTimeFormat("en-US", {
dateStyle: "long",
});

export default async function Page({ params }: Props) {
const article = await getArticleFromParams(params);
if (!article) {
notFound();
}
const source = await getDocMdxSource(article);
const jsonLd = {
"@context": "https://schema.org",
"@type": "NewsArticle",
headline: article.title,
image: [article.image.src],
datePublished: article.date,
dateModified: article.updatedAt ?? article.date,
author: [
{
"@type": "Person",
name: article.author,
},
],
};
return (
<article
className="prose mx-auto mb-24 mt-14 max-w-none dark:prose-invert"
style={{ contain: "none" }}
>
<>
<article
className="prose mx-auto mb-24 mt-14 max-w-none dark:prose-invert"
style={{ contain: "none" }}
>
<Container tight>
<header>
{article.category && (
<div className="mb-4 font-medium text-violet-11">
{article.category}
</div>
)}
<h1>{article.title}</h1>
<div className="my-4 flex items-center gap-2 text-sm text-low">
<time dateTime={article.date}>
{dateFormatter.format(new Date(article.date))}
</time>
<div className="text-xs text-mauve-10">|</div>
<address className="not-italic">{article.author}</address>
</div>
</header>
{source}
</Container>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
</article>
<Container tight>
{article.category && (
<div className="mb-4 font-medium text-violet-11">
{article.category}
</div>
)}
<h1>{article.title}</h1>
<div className="my-4 flex items-center gap-2 text-sm text-low">
{new Intl.DateTimeFormat("en-US", {
dateStyle: "long",
}).format(new Date(article.date))}
<div className="text-xs text-mauve-10">|</div>
{article.author}
</div>
{source}
<Siblings slug={article.slug} />
</Container>
</article>
</>
);
}
18 changes: 6 additions & 12 deletions app/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ import {
} from "@/components/PostCard";
import { getArticles } from "@/lib/blog-api";

const formatDate = (date: string) => {
return new Intl.DateTimeFormat("en-US", {
dateStyle: "medium",
}).format(new Date(date));
};

export const metadata: Metadata = {
metadataBase: new URL("https://argos-ci.com"),
title: "Updates from the Argos team",
Expand All @@ -45,8 +39,8 @@ export default async function Page() {
</h1>
</div>
<div className="mt-12 grid grid-cols-2 gap-x-16 gap-y-20">
<Link href={`/blog/${firstArticle.slug}`} className="col-span-2">
<PostCard>
<Link href={`/blog/${firstArticle.slug}`} className="contents">
<PostCard className="col-span-2">
<PostCardImage
width={firstArticle.image.width}
height={firstArticle.image.height}
Expand All @@ -65,7 +59,7 @@ export default async function Page() {
<PostCardFooter>
<PostCardAuthor>{firstArticle.author}</PostCardAuthor>
<span className="text-low">|</span>
<PostCardDate>{formatDate(firstArticle.date)}</PostCardDate>
<PostCardDate date={firstArticle.date} />
</PostCardFooter>
</PostCardBody>
</PostCard>
Expand All @@ -76,9 +70,9 @@ export default async function Page() {
<Link
key={article.slug}
href={`/blog/${article.slug}`}
className="col-span-2 md:col-span-1"
className="contents"
>
<PostCard>
<PostCard className="col-span-2 md:col-span-1">
<PostCardImage
width={article.image.width}
height={article.image.height}
Expand All @@ -96,7 +90,7 @@ export default async function Page() {
<PostCardFooter>
<PostCardAuthor>{article.author}</PostCardAuthor>
<div className="text-xs text-mauve-10">|</div>
<PostCardDate>{formatDate(article.date)}</PostCardDate>
<PostCardDate date={article.date} />
</PostCardFooter>
</PostCardBody>
</PostCard>
Expand Down
17 changes: 17 additions & 0 deletions app/blog/sitemap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { MetadataRoute } from "next";

import { getArticles } from "@/lib/blog-api";

const BASE_URL = "https://argos-ci.com";

export default async function sitemap({
id,
}: {
id: number;
}): Promise<MetadataRoute.Sitemap> {
const articles = await getArticles();
return articles.map((article) => ({
url: `${BASE_URL}/blog/${article.slug}`,
lastModified: article.date,
}));
}
6 changes: 6 additions & 0 deletions app/sitemap.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>https://argos-ci.com/main-sitemap.xml</loc></sitemap>
<sitemap><loc>https://argos-ci.com/blog/sitemap.xml</loc></sitemap>
<sitemap><loc>https://argos-ci.com/docs/sitemap.xml</loc></sitemap>
</sitemapindex>
31 changes: 19 additions & 12 deletions components/PostCard.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import { clsx } from "clsx";
import Image, { ImageProps } from "next/image";
import type { ComponentProps, ReactNode } from "react";
import { twc } from "react-twc";

export const PostCard = ({ children }: { children: ReactNode }) => (
<div
className={clsx(
"rounded-lg border bg-subtle text-left transition duration-300 hover:-translate-y-2 hover:scale-[101%]",
)}
>
{children}
</div>
);
export const PostCard = twc.div`rounded-lg border bg-subtle text-left transition duration-300 hover:-translate-y-2 hover:scale-[101%]"`;

export interface PostCardImageProps extends ImageProps {
extended?: Boolean;
Expand Down Expand Up @@ -83,6 +76,20 @@ export const PostCardAuthor = (props: ComponentProps<"div">) => (
<div className="text-low" {...props} />
);

export const PostCardDate: React.FC<{ children: React.ReactNode }> = (
props,
) => <div className="text-low" {...props} />;
const formatDate = (date: string) => {
return new Intl.DateTimeFormat("en-US", {
dateStyle: "medium",
}).format(new Date(date));
};

const dateFormatter = new Intl.DateTimeFormat("en-US", {
dateStyle: "medium",
});

export const PostCardDate = ({ date }: { date: string }) => {
return (
<time className="text-low" dateTime={date}>
{dateFormatter.format(new Date(date))}
</time>
);
};
3 changes: 3 additions & 0 deletions lib/blog-api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const FrontmatterSchema = z.object({
title: z.string(),
description: z.string(),
date: z.date(),
updatedAt: z.date().optional(),
author: z.string(),
image: z.string(),
imageAlt: z.string(),
Expand All @@ -26,6 +27,7 @@ export type Article = {
title: string;
description: string;
date: string;
updatedAt: string | null;
author: string;
image: StaticImageData;
imageAlt: string;
Expand Down Expand Up @@ -72,6 +74,7 @@ async function getArticleDataFromPath(
description: frontmatter.description,
slug,
date: frontmatter.date.toISOString(),
updatedAt: frontmatter.updatedAt?.toISOString() ?? null,
author: frontmatter.author,
category: frontmatter.category,
};
Expand Down
5 changes: 5 additions & 0 deletions next-sitemap.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: process.env.SITE_URL || "https://argos-ci.com",
generateRobotsTxt: false,
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"next": "^14.0.1",
"next-mdx-remote": "^4.4.1",
"next-plausible": "^3.11.3",
"next-sitemap": "^4.2.3",
"next-themes": "^0.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
26 changes: 25 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions public/main-sitemap.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
<url><loc>https://argos-ci.com/playwright</loc><lastmod>2023-12-28T05:44:31.942Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://argos-ci.com/pricing</loc><lastmod>2023-12-28T05:44:31.942Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
<url><loc>https://argos-ci.com/blog</loc><lastmod>2023-12-28T05:44:31.942Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
</urlset>

0 comments on commit 5bee326

Please sign in to comment.