From 70a847269bcbe326be798881014c716f14cdf474 Mon Sep 17 00:00:00 2001 From: Matteo Giardino Date: Tue, 12 Mar 2024 17:19:29 +0100 Subject: [PATCH] feat: seo --- app/(core)/blog/[slug]/page.tsx | 42 +++-- app/(core)/blog/page.tsx | 57 +++--- app/(core)/contacts/page.tsx | 36 ++++ app/(core)/layout.tsx | 5 +- app/(core)/learn/page.tsx | 36 ++++ app/(core)/posts/[slug]/page.tsx | 92 +++++++++- app/(core)/projects/[slug]/page.tsx | 8 +- app/(core)/projects/page.tsx | 35 ++++ app/(core)/services/page.tsx | 35 ++++ .../programmatore-leggendario/layout.tsx | 53 +++++- app/(marketing)/tcb/layout.tsx | 53 +++++- app/(marketing)/tcl/layout.tsx | 54 +++++- app/layout.tsx | 33 +++- app/robots.ts | 17 ++ app/rss.xml/route.ts | 37 ++++ app/sitemap.ts | 37 ++++ components/progress-provider.tsx | 12 ++ config/routes.ts | 3 +- config/site.ts | 162 +++++++++++++++--- lib/blog.ts | 1 + lib/substack.ts | 10 +- package.json | 3 + pnpm-lock.yaml | 46 +++++ .../images/favicon/android-icon-144x144.png | Bin 0 -> 2911 bytes .../images/favicon/android-icon-192x192.png | Bin 0 -> 2877 bytes public/images/favicon/android-icon-36x36.png | Bin 0 -> 1047 bytes public/images/favicon/android-icon-48x48.png | Bin 0 -> 1238 bytes public/images/favicon/android-icon-72x72.png | Bin 0 -> 1596 bytes public/images/favicon/android-icon-96x96.png | Bin 0 -> 2015 bytes public/images/favicon/apple-icon-120x120.png | Bin 0 -> 2419 bytes public/images/favicon/apple-icon-180x180.png | Bin 0 -> 3750 bytes public/images/favicon/apple-icon-60x60.png | Bin 0 -> 1426 bytes .../images/favicon/apple-icon-precomposed.png | Bin 0 -> 3415 bytes public/images/favicon/apple-icon.png | Bin 0 -> 3415 bytes public/images/favicon/browserconfig.xml | 11 ++ public/images/favicon/favicon-16x16.png | Bin 0 -> 741 bytes public/images/favicon/favicon-32x32.png | Bin 0 -> 964 bytes public/images/favicon/favicon-96x96.png | Bin 0 -> 2015 bytes public/images/favicon/favicon.ico | Bin 0 -> 1150 bytes public/images/favicon/manifest.json | 41 +++++ public/images/favicon/ms-icon-144x144.png | Bin 0 -> 2911 bytes public/images/favicon/ms-icon-150x150.png | Bin 0 -> 3058 bytes public/images/favicon/ms-icon-310x310.png | Bin 0 -> 8282 bytes public/images/favicon/ms-icon-70x70.png | Bin 0 -> 1583 bytes types/blog.ts | 1 + 45 files changed, 832 insertions(+), 88 deletions(-) create mode 100644 app/robots.ts create mode 100644 app/rss.xml/route.ts create mode 100644 app/sitemap.ts create mode 100644 components/progress-provider.tsx create mode 100644 public/images/favicon/android-icon-144x144.png create mode 100644 public/images/favicon/android-icon-192x192.png create mode 100644 public/images/favicon/android-icon-36x36.png create mode 100644 public/images/favicon/android-icon-48x48.png create mode 100644 public/images/favicon/android-icon-72x72.png create mode 100644 public/images/favicon/android-icon-96x96.png create mode 100644 public/images/favicon/apple-icon-120x120.png create mode 100644 public/images/favicon/apple-icon-180x180.png create mode 100644 public/images/favicon/apple-icon-60x60.png create mode 100644 public/images/favicon/apple-icon-precomposed.png create mode 100644 public/images/favicon/apple-icon.png create mode 100644 public/images/favicon/browserconfig.xml create mode 100644 public/images/favicon/favicon-16x16.png create mode 100644 public/images/favicon/favicon-32x32.png create mode 100644 public/images/favicon/favicon-96x96.png create mode 100644 public/images/favicon/favicon.ico create mode 100644 public/images/favicon/manifest.json create mode 100644 public/images/favicon/ms-icon-144x144.png create mode 100644 public/images/favicon/ms-icon-150x150.png create mode 100644 public/images/favicon/ms-icon-310x310.png create mode 100644 public/images/favicon/ms-icon-70x70.png diff --git a/app/(core)/blog/[slug]/page.tsx b/app/(core)/blog/[slug]/page.tsx index f3a79a6..3b909e7 100644 --- a/app/(core)/blog/[slug]/page.tsx +++ b/app/(core)/blog/[slug]/page.tsx @@ -1,13 +1,12 @@ -import { Metadata } from 'next' +import { Metadata, ResolvingMetadata } from 'next' import { notFound } from 'next/navigation' import { absoluteUrl } from '@/utils/urls' -import { allBlogPosts } from 'contentlayer/generated' import { type Article, type WithContext } from 'schema-dts' import { BlogPostSource } from '@/types/blog' import { Routes } from '@/config/routes' import { site } from '@/config/site' -import { getLocalBlogPost } from '@/lib/blog' +import { getLocalBlogPost, getLocalBlogPosts } from '@/lib/blog' import { Footer } from '@/components/blog/post-footer' import { Header } from '@/components/blog/post-header' import { Content } from '@/components/mdx-content' @@ -19,20 +18,19 @@ type Props = { } } -export const generateStaticParams = () => { - return allBlogPosts.map((post) => ({ +export function generateStaticParams() { + const posts = getLocalBlogPosts() + return posts.map((post) => ({ slug: post.slug, })) } -export const generateMetadata = (props: Props): Metadata => { - const { params } = props +export async function generateMetadata({ params }: Props, parent: ResolvingMetadata): Promise { + const previousOpenGraph = (await parent)?.openGraph ?? {} + const previousTwitter = (await parent)?.twitter ?? {} - const post = allBlogPosts.find((p) => p.slug === params.slug) - - if (!post) { - return {} - } + const post = getLocalBlogPost(params.slug) + if (!post) return {} const ISOPublishedTime = new Date(post.createdAt).toISOString() const ISOModifiedTime = new Date(post.modifiedAt).toISOString() @@ -41,13 +39,14 @@ export const generateMetadata = (props: Props): Metadata => { title: post.title, description: post.summary, alternates: { - canonical: `${site.url}/blog/${params.slug}`, + canonical: absoluteUrl(Routes.LocalBlogPost(params.slug)), }, openGraph: { - url: `${site.url}/blog/${params.slug}`, + ...previousOpenGraph, + url: absoluteUrl(Routes.LocalBlogPost(params.slug)), type: 'article', title: post.title, - siteName: site.name, + siteName: site.title, description: post.summary, locale: 'it-IT', publishedTime: ISOPublishedTime, @@ -63,6 +62,19 @@ export const generateMetadata = (props: Props): Metadata => { }, ], }, + twitter: { + ...previousTwitter, + title: post.title, + description: post.summary, + images: [ + { + url: `${site.url}/images/blog/${post.slug}/og.png`, + alt: post.summary, + width: 1200, + height: 630, + }, + ], + }, } } diff --git a/app/(core)/blog/page.tsx b/app/(core)/blog/page.tsx index 2aad764..39e2114 100644 --- a/app/(core)/blog/page.tsx +++ b/app/(core)/blog/page.tsx @@ -1,38 +1,43 @@ -import type { Metadata } from 'next' +import type { Metadata, ResolvingMetadata } from 'next' +import { absoluteUrl } from '@/utils/urls' -import { site } from '@/config/site' +import { Routes } from '@/config/routes' import { getAllBlogPosts } from '@/lib/blog' import { FilteredPosts } from '@/components/blog/filtered-posts' import { SubscribeForm } from '@/components/blog/subscribe-form' import { PageTitle } from '@/components/page-title' -const title = 'Articoli' +const title = 'articoli' const description = - 'Sono onesto, la scrittura non è il mio punto forte. Proprio per questo, ho deciso di sfidare me stesso creando questo piccolo angolo del web, un posto dove allenarmi a scrivere. Tanto chi legge i blog nel 2023?' - -export const metadata: Metadata = { - title, - description, - alternates: { - canonical: `${site.url}/blog`, - }, - openGraph: { - url: `${site.url}/blog`, - type: 'website', + 'un remoto angolo del web che posso riempire di articoli, storie e guide. Tanto chi legge i blog nel 2024?' + +type Props = { + params: Record + searchParams: Record +} + +export async function generateMetadata(_: Props, parent: ResolvingMetadata): Promise { + const previousOpenGraph = (await parent)?.openGraph ?? {} + const previousTwitter = (await parent)?.twitter ?? {} + + return { title, - siteName: site.title, description, - locale: 'it-IT', - images: [ - { - url: `${site.url}/images/og/og.png`, - width: 1200, - height: 630, - alt: description, - type: 'image/png', - }, - ], - }, + alternates: { + canonical: absoluteUrl(Routes.Blog), + }, + openGraph: { + ...previousOpenGraph, + url: absoluteUrl(Routes.Blog), + title, + description, + }, + twitter: { + ...previousTwitter, + title, + description, + }, + } } export default async function Page() { diff --git a/app/(core)/contacts/page.tsx b/app/(core)/contacts/page.tsx index 3d6734f..bdb43ac 100644 --- a/app/(core)/contacts/page.tsx +++ b/app/(core)/contacts/page.tsx @@ -1,11 +1,47 @@ +import { Metadata, ResolvingMetadata } from 'next' import Image from 'next/image' import Link from 'next/link' +import { absoluteUrl } from '@/utils/urls' import { IconExternalLink } from '@tabler/icons-react' import { ContactLinks, SocialLinks } from '@/config/links' +import { Routes } from '@/config/routes' import { Icon } from '@/components/icon' import { PageTitle } from '@/components/page-title' +const title = 'contatti' +const description = + 'che tu voglia conoscermi, parlarmi di un progetto o una collaborazione, scrivimi. Nasce sempre qualcosa di bello da un messaggio.' + +type Props = { + params: Record + searchParams: Record +} + +export async function generateMetadata(_: Props, parent: ResolvingMetadata): Promise { + const previousOpenGraph = (await parent)?.openGraph ?? {} + const previousTwitter = (await parent)?.twitter ?? {} + + return { + title, + description, + alternates: { + canonical: absoluteUrl(Routes.Contact), + }, + openGraph: { + ...previousOpenGraph, + url: absoluteUrl(Routes.Contact), + title, + description, + }, + twitter: { + ...previousTwitter, + title, + description, + }, + } +} + export default function Page() { return ( <> diff --git a/app/(core)/layout.tsx b/app/(core)/layout.tsx index 9c4cb15..697fc13 100644 --- a/app/(core)/layout.tsx +++ b/app/(core)/layout.tsx @@ -2,8 +2,7 @@ import * as React from 'react' import { Footer } from '@/components/footer' import { Header } from '@/components/header' - -// import { NotificationPopup } from '@/components/notification-popup' +import { NotificationPopup } from '@/components/notification-popup' export default function Layout({ children }: { children: React.ReactNode }) { return ( @@ -11,7 +10,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
{children}