OpenGraph.tsx
export type OGProperties = {
locale?: "en_US" | "fr_FR";
url: string;
title: string;
type: "article" | "website";
description: string;
site_name: string;
image: {
alt: string;
type: string;
url: string;
width: string;
height: string;
} | null;
author?: string;
section?: string;
modified_time?: string;
published_time?: string;
card: "summary" | "summary_large_image" | "app" | "player";
};
const OpenGraph = ({ properties }: { properties: OGProperties }) => {
const {
locale,
url,
site_name,
title,
type,
description,
author,
section,
image,
modified_time,
published_time,
card,
} = properties;
return (
<>
<meta property="og:locale" content={locale || "en_US"} />
<meta property="og:title" content={title} />
<meta property="og:type" content={type} />
<meta property="og:description" content={description || ""} />
<meta property="og:url" content={url} />
<meta property="og:site_name" content={site_name} />
{type === "article" && (
<>
<meta property="article:author" content={author} />
<meta property="article:section" content={section} />
<meta property="article:modified_time" content={modified_time} />
<meta property="article:published_time" content={published_time} />
</>
)}
{image && (
<>
<meta property="og:image" content={image.url} />
<meta
property="og:image:secure_url"
content={image.url.replace("http://", "https://")}
/>
<meta property="og:image:width" content={image.width} />
<meta property="og:image:height" content={image.height} />
<meta property="og:image:alt" content={image.alt} />
<meta property="og:image:type" content={image.type} />
<meta name="twitter:image" content={image.url} />
</>
)}
<meta name="twitter:card" content={card} />
<meta name="twitter:url" content={url} />
<meta name="twitter:domain" content="codersteps.com" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description || ""} />
<meta name="twitter:site" content="@codersteps" />
<meta name="twitter:creator" content="@abdessamadely" />
</>
);
};
export default OpenGraph;
helpers.ts
export const absUrl = (path: string): string => {
path = path.trim();
if (path.startsWith("http")) {
return path;
}
if (path.indexOf("/") === 0) {
path = path.substring(1);
}
const appUrl =
process.env.APP_URL ||
process.env.NEXT_PUBLIC_APP_URL ||
"http://localhost:3000";
return `${appUrl}/${path}`;
};
posts.json
[
{
"id": 1,
"title": "Post 1",
"thumbnail": "/images/og-posts-image.jpg",
"description": "Description of post 1",
"content": "Content of post 1"
},
{
"id": 2,
"title": "Post 2",
"thumbnail": "/images/og-posts-image.jpg",
"description": "Description of post 2",
"content": "Content of post 2"
},
{
"id": 3,
"title": "Post 3",
"thumbnail": "/images/og-posts-image.jpg",
"description": "Description of post 3",
"content": "Content of post 3"
}
]
useOpenGraph.ts
import { useMemo } from "react";
import { absUrl } from "../core/helpers";
import { OGProperties } from "../components/common/OpenGraph";
type OGImage = {
alt: string;
type: string;
url: string;
width?: string;
height?: string;
} | null;
type PageOgData = Omit<OGProperties, "image" | "card" | "site_name"> & {
card?: OGProperties["card"];
image: OGImage;
};
export const useOpenGraph = (data: PageOgData) => {
const ogProperties = useMemo<OGProperties>(() => {
return {
url: data.url,
title: data.title,
type: data.type,
author: data.author,
site_name: "OpenGraph Article",
description: data.description,
image: data.image
? {
type: data.image.type,
url: absUrl(data.image.url),
alt: data.image.alt || "",
height: data.image.height || "720",
width: data.image.width || "420",
}
: null,
card: data.card || data.image ? "summary_large_image" : "summary",
section: data.section,
modified_time: data.modified_time,
published_time: data.published_time,
};
}, [data]);
return ogProperties;
};
export default useOpenGraph;
hello.ts
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'
type Data = {
name: string
}
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
res.status(200).json({ name: 'John Doe' })
}
[id].tsx
import Head from "next/head";
import Image from "next/image";
import { GetServerSideProps, NextPage } from "next";
import OpenGraph from "../../components/common/OpenGraph";
import useOpenGraph from "../../hooks/useOpenGraph";
import { absUrl } from "../../core/helpers";
type Post = {
id: number;
title: string;
thumbnail: string;
description: string;
content: string;
};
interface ServerSideProps {
post: Post;
}
const ShowPost: NextPage<ServerSideProps> = ({ post }) => {
const ogProperties = useOpenGraph({
url: absUrl(`/posts/${post.id}`),
title: post.title,
image: {
// The post thumbnail
type: "image/jpeg", // replace it with a dynamic thumbnail mimetype
url: post.thumbnail,
alt: post.title,
},
description: post.description,
type: "article",
author: "Article Author",
section: "Article Category",
modified_time: "Post Updated Time",
published_time: "Post Published Time",
});
return (
<>
<Head>
<title>{post.title}</title>
<OpenGraph properties={ogProperties} />
</Head>
<main>
<article>
<h1>{post.title}</h1>
<Image
width={720}
height={420}
src={post.thumbnail}
alt={post.title}
/>
<div dangerouslySetInnerHTML={{ __html: post.content }}></div>
</article>
</main>
</>
);
};
export const getServerSideProps: GetServerSideProps<ServerSideProps> = async ({
params,
}) => {
if (!params || typeof params.id !== "string" || isNaN(+params.id)) {
return { notFound: true };
}
const posts: Post[] = require("../../data/posts.json");
const postId = +params.id;
const post = posts.find((p) => p.id === postId);
if (!post) {
return { notFound: true };
}
return {
props: {
post,
},
};
};
export default ShowPost;
_app.tsx
import '../styles/globals.css'
import type { AppProps } from 'next/app'
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
export default MyApp
index.tsx
import type { NextPage } from "next";
import Head from "next/head";
import OpenGraph from "../components/common/OpenGraph";
import { useOpenGraph } from "../hooks/useOpenGraph";
import { absUrl } from "../core/helpers";
const Home: NextPage = () => {
const ogProperties = useOpenGraph({
url: absUrl("/"),
title: "OpenGraph Article", // Add you homepage title
image: {
// some default image preview for your website
type: "image/jpeg",
url: "/assets/images/ogImage.jpg",
alt: "Image Alt",
},
description: "Your website description",
type: "website",
});
return (
<div>
<Head>
<title>OpenGraph with Next.js</title>
<meta
name="description"
content="Article about OpenGraph with Next.js"
/>
<OpenGraph properties={ogProperties} />
</Head>
<main>
<h1>OpenGraph with Next.js</h1>
<div>An empty page is a good page</div>
</main>
</div>
);
};
export default Home;
globals.css
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
.eslintrc.json
{
"extends": "next/core-web-vitals"
}
next-env.d.ts
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
module.exports = nextConfig
package.json
{
"name": "open_graph",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "12.1.6",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@types/node": "18.0.0",
"@types/react": "18.0.14",
"@types/react-dom": "18.0.5",
"eslint": "8.18.0",
"eslint-config-next": "12.1.6",
"typescript": "4.7.4"
}
}tsconfig.json
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}