diff --git a/lib/consts.ts b/lib/consts.ts
index 4d11320..0fab30e 100644
--- a/lib/consts.ts
+++ b/lib/consts.ts
@@ -1,2 +1,4 @@
 export const BASE_URL =
-  typeof window !== "undefined" ? location.protocol + "//" + location.host : "";
+  typeof window !== "undefined"
+    ? location.protocol + "//" + location.host
+    : process.env.BASE_URL || "";
diff --git a/pages/project/@slug/+data.ts b/pages/project/@slug/+data.ts
index 9c3281e..be039a9 100644
--- a/pages/project/@slug/+data.ts
+++ b/pages/project/@slug/+data.ts
@@ -1,6 +1,7 @@
 import { PageContext } from "vike/types";
 import { render } from "vike/abort";
 import trpcServer from "~/server/api/trpc/trpc";
+import { BASE_URL } from "~/lib/consts";
 
 export const data = async (ctx: PageContext) => {
   const trpc = await trpcServer(ctx);
@@ -20,6 +21,9 @@ export const data = async (ctx: PageContext) => {
   return {
     title: project.title,
     description: `Check ${project.title} on CodeShare!`,
+    ogImage: project.thumbnail
+      ? BASE_URL + "/api/thumbnail" + project.thumbnail
+      : undefined,
     project,
     files,
     initialFiles,
diff --git a/renderer/+onRenderClient.tsx b/renderer/+onRenderClient.tsx
index 8382dea..e47aab0 100644
--- a/renderer/+onRenderClient.tsx
+++ b/renderer/+onRenderClient.tsx
@@ -1,5 +1,5 @@
 import ReactDOM from "react-dom/client";
-import { getPageMetadata } from "./utils";
+import { getPageTitle } from "./utils";
 import type { OnRenderClientAsync } from "vike/types";
 import Layout from "./app";
 
@@ -33,6 +33,5 @@ export const onRenderClient: OnRenderClientAsync = async (
     root.render(page);
   }
 
-  const meta = getPageMetadata(pageContext);
-  document.title = meta.title;
+  document.title = getPageTitle(pageContext);
 };
diff --git a/renderer/+onRenderHtml.tsx b/renderer/+onRenderHtml.tsx
index 982a500..7aef567 100644
--- a/renderer/+onRenderHtml.tsx
+++ b/renderer/+onRenderHtml.tsx
@@ -21,7 +21,7 @@ export const onRenderHtml: OnRenderHtmlAsync = async (
   );
 
   // See https://vike.dev/head
-  const meta = getPageMetadata(pageContext);
+  const metadata = getPageMetadata(pageContext);
 
   const documentHtml = escapeInject`<!DOCTYPE html>
     <html lang="en" class="dark">
@@ -29,8 +29,7 @@ export const onRenderHtml: OnRenderHtmlAsync = async (
         <meta charset="UTF-8" />
         <link rel="icon" href="/favicon.ico" />
         <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-        <meta name="description" content="${meta.description}" />
-        <title>${meta.title}</title>
+        ${dangerouslySkipEscape(metadata)}
       </head>
       <body>
         <div id="react-root">${dangerouslySkipEscape(page)}</div>
diff --git a/renderer/types.ts b/renderer/types.ts
index 9c2bd51..ff51956 100644
--- a/renderer/types.ts
+++ b/renderer/types.ts
@@ -8,6 +8,7 @@ declare global {
       data?: {
         title?: string;
         description?: string;
+        ogImage?: string;
       };
       config: {
         title?: string;
diff --git a/renderer/utils.ts b/renderer/utils.ts
index 9aadfe2..714ec9c 100644
--- a/renderer/utils.ts
+++ b/renderer/utils.ts
@@ -1,13 +1,40 @@
 import type { PageContext } from "vike/types";
+import { BASE_URL } from "~/lib/consts";
 
-export function getPageMetadata(pageContext: PageContext) {
+export function getPageTitle(pageContext: PageContext) {
   let title = pageContext.data?.title || pageContext.config.title;
   title = title ? `${title} - CodeShare` : "Welcome to CodeShare";
 
+  return title;
+}
+
+export function getPageMetadata(pageContext: PageContext) {
+  const canonicalUrl = BASE_URL + pageContext.req.url;
+  const title = getPageTitle(pageContext);
   const description =
     pageContext.data?.description ||
     pageContext.config.description ||
     "Share your frontend result with everyone";
+  const ogImage = pageContext.data?.ogImage;
 
-  return { title, description };
+  return `
+    <meta name="description" content="${description}" />
+    <title>${title}</title>
+
+    <link rel="canonical" href="${canonicalUrl}" />
+
+    <!-- Open Graph / Facebook -->
+    <meta property="og:type" content="website" />
+    <meta property="og:url" content="${canonicalUrl}" />
+    <meta property="og:title" content="${title}" />
+    <meta property="og:description" content="${description}" />
+    ${ogImage ? `<meta property="og:image" content="${ogImage}" />` : ""}
+
+    <!-- Twitter -->
+    <meta property="twitter:card" content="summary_large_image" />
+    <meta property="twitter:url" content="${canonicalUrl}" />
+    <meta property="twitter:title" content="${title}" />
+    <meta property="twitter:description" content="${description}" />
+    ${ogImage ? `<meta property="twitter:image" content="${ogImage}" />` : ""}
+  `.trim();
 }