Code
import Head from 'next/head';
import { useRouter } from 'next/router';
import { openGraph } from '@/lib/helper';
const defaultMeta = {
title: 'Next.js + Tailwind CSS + TypeScript Starter',
siteName: 'Next.js + Tailwind CSS + TypeScript Starter',
description:
' A starter for Next.js, Tailwind CSS, and TypeScript with Absolute Import, Seo, Link component, pre-configured with Husky',
url: 'https://tsnext-tw.thcl.dev',
image: 'https://edisonpadilla.com/favicon/large-og.jpg',
type: 'website',
robots: 'follow, index',
};
type SeoProps = {
date?: string;
templateTitle?: string;
} & Partial<typeof defaultMeta>;
export default function Seo(props: SeoProps) {
const router = useRouter();
const meta = {
...defaultMeta,
...props,
};
meta['title'] = props.templateTitle
? `${props.templateTitle} | ${meta.siteName}`
: meta.title;
// Use siteName if there is templateTitle
// but show full title if there is none
meta.image = openGraph({
description: meta.description,
siteName: props.templateTitle ? meta.siteName : meta.title,
templateTitle: props.templateTitle,
});
return (
<Head>
<title>{meta.title}</title>
<meta name='robots' content={meta.robots} />
<meta content={meta.description} name='description' />
<meta property='og:url' content={`${meta.url}${router.asPath}`} />
<link rel='canonical' href={`${meta.url}${router.asPath}`} />
{/* Open Graph */}
<meta property='og:type' content={meta.type} />
<meta property='og:site_name' content={meta.siteName} />
<meta property='og:description' content={meta.description} />
<meta property='og:title' content={meta.title} />
<meta name='image' property='og:image' content={meta.image} />
{/* Twitter */}
<meta name='twitter:card' content='summary_large_image' />
<meta name='twitter:site' content='@th_clarence' />
<meta name='twitter:title' content={meta.title} />
<meta name='twitter:description' content={meta.description} />
<meta name='twitter:image' content={meta.image} />
{meta.date && (
<>
<meta property='article:published_time' content={meta.date} />
<meta
name='publish_date'
property='og:publish_date'
content={meta.date}
/>
<meta
name='author'
property='article:author'
content='Edison J. Padilla'
/>
</>
)}
{/* Favicons */}
{favicons.map((linkProps) => (
<link key={linkProps.href} {...linkProps} />
))}
<meta name='msapplication-TileColor' content='#ffffff' />
<meta
name='msapplication-TileImage'
content='/favicon/ms-icon-144x144.png'
/>
<meta name='theme-color' content='#ffffff' />
</Head>
);
}
type Favicons = {
rel: string;
href: string;
sizes?: string;
type?: string;
};
const favicons: Array<Favicons> = [
{
rel: 'apple-touch-icon',
sizes: '57x57',
href: '/favicon/apple-icon-57x57.png',
},
{
rel: 'apple-touch-icon',
sizes: '60x60',
href: '/favicon/apple-icon-60x60.png',
},
{
rel: 'apple-touch-icon',
sizes: '72x72',
href: '/favicon/apple-icon-72x72.png',
},
{
rel: 'apple-touch-icon',
sizes: '76x76',
href: '/favicon/apple-icon-76x76.png',
},
{
rel: 'apple-touch-icon',
sizes: '114x114',
href: '/favicon/apple-icon-114x114.png',
},
{
rel: 'apple-touch-icon',
sizes: '120x120',
href: '/favicon/apple-icon-120x120.png',
},
{
rel: 'apple-touch-icon',
sizes: '144x144',
href: '/favicon/apple-icon-144x144.png',
},
{
rel: 'apple-touch-icon',
sizes: '152x152',
href: '/favicon/apple-icon-152x152.png',
},
{
rel: 'apple-touch-icon',
sizes: '180x180',
href: '/favicon/apple-icon-180x180.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '192x192',
href: '/favicon/android-icon-192x192.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '32x32',
href: '/favicon/favicon-32x32.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '96x96',
href: '/favicon/favicon-96x96.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '16x16',
href: '/favicon/favicon-16x16.png',
},
{
rel: 'manifest',
href: '/favicon/manifest.json',
},
];
tsx
Usage
import Seo from '@/components/Seo';
// If we don't want to override anything
<Seo />
// Overrides
<Seo title='Your page title' description='your description' />
tsx
You can also add props below to override the default SEO
title
templateTitle
description
image
type
robots
date -> {new Date(your date).toISOString()}
text
Open Graph Function
type OpenGraphType = {
siteName: string;
description: string;
templateTitle?: string;
logo?: string;
};
export function openGraph({
siteName,
templateTitle,
description,
logo = 'https://og.thcl.dev/images/logo.jpg',
}: OpenGraphType): string {
const ogLogo = encodeURIComponent(logo);
const ogSiteName = encodeURIComponent(siteName.trim());
const ogTemplateTitle = templateTitle
? encodeURIComponent(templateTitle.trim())
: undefined;
const ogDesc = encodeURIComponent(description.trim());
return `https://og.thcl.dev/api/general?siteName=${ogSiteName}&description=${ogDesc}&logo=${ogLogo}${
ogTemplateTitle ? `&templateTitle=${ogTemplateTitle}` : ''
}`;
}
ts