Skip to content

Commit

Permalink
feat: seo
Browse files Browse the repository at this point in the history
  • Loading branch information
gatteo committed Mar 12, 2024
1 parent 8b54da4 commit 70a8472
Show file tree
Hide file tree
Showing 45 changed files with 832 additions and 88 deletions.
42 changes: 27 additions & 15 deletions app/(core)/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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<Metadata> {
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()
Expand All @@ -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,
Expand All @@ -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,
},
],
},
}
}

Expand Down
57 changes: 31 additions & 26 deletions app/(core)/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -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<string, never>
searchParams: Record<string, never>
}

export async function generateMetadata(_: Props, parent: ResolvingMetadata): Promise<Metadata> {
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() {
Expand Down
36 changes: 36 additions & 0 deletions app/(core)/contacts/page.tsx
Original file line number Diff line number Diff line change
@@ -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<string, never>
searchParams: Record<string, never>
}

export async function generateMetadata(_: Props, parent: ResolvingMetadata): Promise<Metadata> {
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 (
<>
Expand Down
5 changes: 2 additions & 3 deletions app/(core)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ 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 (
<>
<Header />
<main className='static mx-auto max-w-5xl px-8 py-24 md:mb-16'>{children}</main>
<Footer />
{/* <NotificationPopup /> */}
<NotificationPopup />
</>
)
}
36 changes: 36 additions & 0 deletions app/(core)/learn/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,48 @@
import { Metadata, ResolvingMetadata } from 'next'
import Image from 'next/image'
import Link from 'next/link'
import { absoluteUrl } from '@/utils/urls'
import { ArrowUpRight } from 'lucide-react'

import { products } from '@/config/products'
import { Routes } from '@/config/routes'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { PageTitle } from '@/components/page-title'

const title = 'formazione'
const description =
'ho creato strumenti e risorse che aiutano centinaia di persone ad imparare a programmare e lanciare la loro carriera.'

type Props = {
params: Record<string, never>
searchParams: Record<string, never>
}

export async function generateMetadata(_: Props, parent: ResolvingMetadata): Promise<Metadata> {
const previousOpenGraph = (await parent)?.openGraph ?? {}
const previousTwitter = (await parent)?.twitter ?? {}

return {
title,
description,
alternates: {
canonical: absoluteUrl(Routes.LearningProducts),
},
openGraph: {
...previousOpenGraph,
url: absoluteUrl(Routes.LearningProducts),
title,
description,
},
twitter: {
...previousTwitter,
title,
description,
},
}
}

export default function Page() {
return (
<>
Expand Down
92 changes: 91 additions & 1 deletion app/(core)/posts/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,112 @@
import { Metadata, ResolvingMetadata } from 'next'
import Link from 'next/link'
import { notFound } from 'next/navigation'
import { absoluteUrl } from '@/utils/urls'
import { Article, WithContext } from 'schema-dts'

import { BlogPostSource } from '@/types/blog'
import { getSubstackPost } from '@/lib/substack'
import { Routes } from '@/config/routes'
import { site } from '@/config/site'
import { getSubstackPost, getSubstackPosts } from '@/lib/substack'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Footer } from '@/components/blog/post-footer'
import { Header } from '@/components/blog/post-header'
import { ScrollIndicator } from '@/components/scroll-indicator'

type Props = {
params: {
slug: string
}
}

export async function generateStaticParams() {
const posts = await getSubstackPosts()
return posts.map((post) => ({
slug: post.slug,
}))
}

export async function generateMetadata({ params }: Props, parent: ResolvingMetadata): Promise<Metadata> {
const previousOpenGraph = (await parent)?.openGraph ?? {}
const previousTwitter = (await parent)?.twitter ?? {}

const post = await getSubstackPost(params.slug)
if (!post) return {}

const ISOPublishedTime = new Date(post.date).toISOString()
const ISOModifiedTime = new Date(post.date).toISOString()

return {
title: post.title,
description: post.summary,
alternates: {
canonical: absoluteUrl(Routes.SubstackBlogPost(params.slug)),
},
openGraph: {
...previousOpenGraph,
url: absoluteUrl(Routes.SubstackBlogPost(params.slug)),
type: 'article',
title: post.title,
siteName: site.title,
description: post.summary,
locale: 'it-IT',
publishedTime: ISOPublishedTime,
modifiedTime: ISOModifiedTime,
authors: site.url,
images: [
{
url: post.image,
alt: post.summary,
type: 'image/png',
},
],
},
twitter: {
...previousTwitter,
title: post.title,
description: post.summary,
images: [
{
url: post.image,
alt: post.summary,
},
],
},
}
}

export default async function BlogPostPage({ params: { slug } }: { params: { slug: string } }) {
const post = await getSubstackPost(slug)

if (!post) {
notFound()
}

const jsonLd: WithContext<Article> = {
'@context': 'https://schema.org',
'@type': 'Article',

'headline': post.title,
'description': post.summary,
'datePublished': post.date.toISOString(),
'dateModified': post.date.toISOString(),
'image': post.image,
'author': {
'@type': 'Person',
'name': post.author.name,
'url': post.author.url,
},
'publisher': {
'@type': 'Person',
'name': post.author.name,
'url': post.author.url,
},
}

return (
<>
<script type='application/ld+json' dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />

<Header
createdAt={post.date}
title={post.title}
Expand Down
Loading

0 comments on commit 70a8472

Please sign in to comment.