Its ridiculous to forget about SEO
Another mini tutorial about seo in Next.js

Welcome back to another week of my classic post on SEO. In my graveyard of abandoned projects, this blog ranks first (at the bottom), but today I've taken some time to fix something that's been bothering me for a few months.
As context: A few years ago I rebuilt this blog from scratch using Next.js and Typescript. Previously it was based on Wordpress. The only beautiful thing about this last one is that it gives you a lot of functionality by default that you don't even care about. To the point of taking it for granted. When migrating to a project done "by hand", much of that functionality was lost. In particular, we stopped sending meta information in the head of the page, which is used to generate things like the SERP:

Most of that problem we solved in this post. But at that time we focused on the results in the search engine, but we completely ignored that other sources might want to preview the page. I'm talking about social networks mainly. And what's been bothering me for months is that on LinkedIn, in my profile, this website is displayed like this:

It's not that I'm looking for a job, things are going great at Datadog. In fact, I got this job with that Featured on my LinkedIn profile. But it's pretty funny to sell yourself as a senior frontend engineer and have your cover letter be such a fail. So let's fix it.
Testing
There are many tools to evaluate the metadata of your websites. You can use a website like OpenGraph.xyz, but it obliges you to have the website deployed to do it. That is: You can't evaluate in localhost, which puts you in a rather suboptimal workflow. Luckily we have alternatives like Social share preview, which offers a browser extension for Chrome and Firefox, so you can evaluate your metadata in localhost, in real time.

As you can see, the extension shows the preview of the page on LinkedIn, but also on other social networks like Twitter, Facebook, etc. Let's try to fill in that widget.
Implementing
We have this implementation in layout.tsx:
/app/layout.tsx export const metadata: Metadata = { title: 'Blog de programación | Fran Bosquet', description: baseDescription, icons: '/favicon.ico', metadataBase: new URL('https://www.franbosquet.com'), alternates: { canonical: '/', languages: { 'en-US': 'https://www.en.franbosquet.com' } }, authors: { name: 'Fran Bosquet', url: 'https://www.franbosquet.com' }, publisher: 'Fran Bosquet', robots: 'index, follow' }
As you can see, we are missing a lot of data there. The main thing is that we are not providing a preview image. I'm going to use this image from last year that I like a lot:

And I take the opportunity to add all the missing metadata:
export const metadata: Metadata = { ... twitter: { card: 'summary_large_image', title: 'Blog de programación | Fran Bosquet', description: baseDescription, creator: '@frbosquet', images: ['/images/fran24.webp'] }, openGraph: { description: baseDescription, type: 'website', locale: 'es_ES', siteName: 'Fran Bosquet', images: [ { url: '/images/fran24.webp', width: 700, height: 700, alt: 'Fran Bosquet' } ] } }
This sets a base metadata line that applies to the entire website, but in the posts we are modifying it for the SERP:
app/(es)/posts/[slug]/page.tsx export async function generateMetadata({ params }: Props): Promise<Metadata> { const post = await getPost(params.slug, Lang.ES) return { title: `${post.frontmatter.title as string} | Fran Bosquet`, description: (post.frontmatter.description as string) || baseDescription, robots: 'index, follow', alternates: { canonical: `/posts/${params.slug}` } } }
And here we also add what is missing:
app/(es)/posts/[slug]/page.tsx export async function generateMetadata({ params }: Props): Promise<Metadata> { const post = await getPost(params.slug, Lang.ES) return { ... twitter: { card: 'summary_large_image', title: `${post.frontmatter.title as string} | Fran Bosquet`, description: (post.frontmatter.description as string) || baseDescription, creator: '@frbosquet', images: ['/images/${post.frontmatter.image.src}.webp'] }, openGraph: { description: (post.frontmatter.description as string) || baseDescription, type: 'website', locale: 'es_ES', siteName: 'Fran Bosquet', images: [ { url: `/images/${post.frontmatter.image.src}.webp`, width: 700, height: 700, alt: 'Fran Bosquet' } ] } } }
And here is the result:

Thats for the regular spanish verison of the blog, for the English counterpart (the one you are reading right now) I simply repeated the same in its corresponding route.
We have seen how we can define static metadata and also how to generate them dynamically for the posts. There are more fields than the ones we defined for this entry. If you are interested in the topic, you can dig into the Next.js documentation about metadata or read about the
metatag in MDN
Takeaway
If you have noticed the title of this post, you might have thought that it was ridiculous to have this blog preview on LinkedIn for a couple of years.
The ridiculousness is losing a couple of hours writing this entry just to find out that LinkedIn allows editing the preview aspect through a form. The reading of the metadata only happens when creating the link for the first time, to autocomplete that form:

So, everything I did today wasn't necessary to fix my profile; I could have simply edited the content. But I learned a couple of things, fixed the links for X, Facebook, and LinkedIn itself, and created a new post after a whole year without publishing anything. Besides, I ended up deleting the link and recreating it this time with franbosquet.com sending the correct metadata, so I did end up using everything we implemented.

Thank you for reading this post! See you next time.