├── .eslintrc.json ├── .gitignore ├── README.md ├── components.json ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── assets │ └── logo.png ├── src ├── app │ ├── (component) │ │ ├── code-block.tsx │ │ ├── features.block.tsx │ │ ├── footer.tsx │ │ ├── gtag-script.tsx │ │ ├── link-image-preview.tsx │ │ ├── link-meta-card.tsx │ │ ├── logo.tsx │ │ ├── og-form.tsx │ │ ├── pattern.tsx │ │ └── preview-list.tsx │ ├── app │ │ ├── layout.tsx │ │ └── page.tsx │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── opengraph-image.png │ ├── page.tsx │ └── twitter-image.png ├── components │ ├── layout │ │ └── appbar.layout.tsx │ └── ui │ │ ├── button.tsx │ │ ├── input.tsx │ │ ├── skeleton.tsx │ │ └── textarea.tsx └── lib │ ├── hooks │ └── use-meta.ts │ ├── service │ └── open-service.ts │ ├── type │ └── meta.type.ts │ ├── utils.ts │ └── validator.ts ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Meta Toolkit [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2FTheAlphamerc%2Fmeta-toolkit&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 2 | Meta Toolkit — Preview how your webpage will look on social media 3 | 4 | 5 | With Meta Toolkit you can preview how your webpage will look on Twitter, Facebook, Twitter, Linkedin and more! 6 | 7 | 8 | 9 | ## Introduction 10 | 11 | Meta Toolkit makes it simple to preview and edit meta tags for any URL. Pass a website link to instantly see how it will appear in social media previews. Edit metadata to visualize changes in real-time - all in one simple tool. 12 | 13 | ## Local Development 14 | 15 | To run Meta toolkit locally, you will need to clone this repository 16 | 17 | Once that's done, you can use the following commands to run the app locally: 18 | 19 | ``` 20 | yarn 21 | yarn build 22 | yarn dev 23 | ``` 24 | 25 | ## Tech Stack 26 | 27 | - [Next.js](https://nextjs.org/) – framework 28 | - [Typescript](https://www.typescriptlang.org/) – language 29 | - [Tailwind](https://tailwindcss.com/) – CSS 30 | - [Vercel](https://vercel.com/) – hosting 31 | 32 | ## Contributing 33 | 34 | We love our contributors! Here's how you can contribute: 35 | 36 | - [Open an issue](https://github.com/TheAlphamerc/meta-toolkit/issues) if you believe you've encountered a bug. 37 | - Make a [pull request](https://github.com/TheAlphamerc/meta-toolkit/pull) to add new features/make quality-of-life improvements/fix bugs. 38 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meta-toolkit", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@radix-ui/react-slot": "^1.0.2", 13 | "@types/node": "20.6.0", 14 | "@types/react": "18.2.21", 15 | "@types/react-dom": "18.2.7", 16 | "autoprefixer": "10.4.15", 17 | "class-variance-authority": "^0.7.0", 18 | "clsx": "^2.0.0", 19 | "eslint": "8.49.0", 20 | "eslint-config-next": "13.4.19", 21 | "lucide-react": "^0.274.0", 22 | "next": "13.4.19", 23 | "postcss": "8.4.29", 24 | "react": "18.2.0", 25 | "react-dom": "18.2.0", 26 | "tailwind-merge": "^1.14.0", 27 | "tailwindcss": "3.3.3", 28 | "tailwindcss-animate": "^1.0.7", 29 | "typescript": "5.2.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAlphamerc/meta-toolkit/29ae2e4fea30087df81b2ae935cc8176b3f91ad6/public/assets/logo.png -------------------------------------------------------------------------------- /src/app/(component)/code-block.tsx: -------------------------------------------------------------------------------- 1 | import { Meta } from "@/lib/type/meta.type"; 2 | 3 | export default function CodeBlock({ meta }: { meta: Meta }) { 4 | return ( 5 |
6 |
7 |
8 | Code 9 |
10 | 34 |
35 | 36 |
37 |
41 | <!-- HTML Meta Tags --> 42 |
43 | < 44 | title 45 | > 46 | {meta.title} 47 | </ 48 | title 49 | > 50 |
51 | 56 | 57 |
58 | 59 | 60 | <!-- Facebook Meta Tags --> 61 | 62 |
63 | 64 | 65 | 66 | 67 | 68 |
69 | 70 | 71 | <!-- Twitter Meta Tags --> 72 | 73 |
74 | 79 | 80 | 81 | 86 | 91 | 96 |
97 | 98 | <!-- Meta Tags Generated with Meta tool --> 99 | 100 |
101 |
102 |
103 |
104 | ); 105 | } 106 | 107 | function Meta({ 108 | tag = "meta", 109 | property, 110 | content, 111 | propertyName = "property", 112 | }: { 113 | tag?: string; 114 | property: string; 115 | propertyName?: string; 116 | content?: string; 117 | }) { 118 | return ( 119 | 120 | < 121 | {tag} 122 | {/* PROPERTY */} 123 | 124 |  {propertyName} 125 | =" 126 | {property} 127 | " 128 | 129 | 130 | {/* CONTENT */} 131 | 132 |  content 133 | =" 134 | {content} 135 | " 136 | 137 | > 138 |
139 |
140 | ); 141 | } 142 | -------------------------------------------------------------------------------- /src/app/(component)/features.block.tsx: -------------------------------------------------------------------------------- 1 | export default function Features() { 2 | return ( 3 |
4 |
5 |
6 |
7 |
8 |
9 | 10 |

11 | What is an Open Graph 12 |

13 |
14 |
15 | Open Graph allows websites to provide rich media content when 16 | shared on social platforms like Facebook, Twitter, LinkedIn, and 17 | more. By utilizing Open Graph tags in your HTML code, you can 18 | control how information from your website appears when it's 19 | shared across various social media channels. 20 |
21 |
22 |
23 |
24 | 25 |

26 | What is the image size for Open Graph? 27 |

28 |
29 |
30 | Use images that are at least 1080 pixels in width for best 31 | display on high resolution devices. Facebook recommends using 32 | 1:1 images in your ad creatives for better performance with 33 | image link ads. 34 |
35 |
36 |
37 |
38 | 39 |

40 | How to add OG tags in my site? 41 |

42 |
43 |
44 | Use a free tool such as meta-toolkit.vercel.app to generate 45 | opengraph tags for your website, and copy the meta tags into the 46 | <head> section tag. 47 |
48 |
49 |
50 |
51 | 52 |

53 | Why are Open Graph tags important? 54 |

55 |
56 |
57 | People are arguably more likely to see and click shared content 58 | with optimized OG tags, which means more social media traffic to 59 | your website. They make content more eye-catching in social 60 | media feeds 61 |
62 |
63 |
64 |
65 |
66 |
67 | ); 68 | function Checkbox() { 69 | return ( 70 |
71 | 72 | 79 | 85 | 86 | 87 |
88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/app/(component)/footer.tsx: -------------------------------------------------------------------------------- 1 | import { Pattern2 } from "./pattern"; 2 | 3 | export default function Footer() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | function OSSSection() { 11 | return ( 12 | 13 |
17 |
18 |
19 |

20 | Proudly open-source 21 |

22 |

23 | Our source code is available on GitHub - feel free to read, 24 | review, or contribute to it however you want! 25 |

26 |
27 |
28 | 33 |
34 |
35 | 43 | 44 | 45 |

Github

46 |
47 |
48 |
49 |
50 |
51 |
52 | © 2023 Toolkit{" "} 53 |
54 |
55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/app/(component)/gtag-script.tsx: -------------------------------------------------------------------------------- 1 | import Script from "next/script"; 2 | 3 | function GTagScript() { 4 | const GA_MEASUREMENT_ID = "G-CT2M6878XY"; 5 | return ( 6 | <> 7 | 19 | 20 | ); 21 | } 22 | 23 | export default GTagScript; 24 | -------------------------------------------------------------------------------- /src/app/(component)/link-image-preview.tsx: -------------------------------------------------------------------------------- 1 | import cx from "clsx"; 2 | import Image from "next/image"; 3 | import { memo } from "react"; 4 | 5 | interface ImageViewProps { 6 | src: string; 7 | type?: "small" | "large"; 8 | variant?: "twitter" | "facebook" | "linkedin" | "google"; 9 | } 10 | export default memo(function LinkImagePreview({ 11 | src, 12 | type, 13 | variant, 14 | }: ImageViewProps) { 15 | return ( 16 | {"Preview"} { 30 | e.currentTarget.src = 31 | "https://placehold.co/360x150/F8F8FF/A9A9A9/png?text=No+Preview"; 32 | }} 33 | /> 34 | ); 35 | }); 36 | -------------------------------------------------------------------------------- /src/app/(component)/link-meta-card.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useMeta } from "@/lib/hooks/use-meta"; 4 | import { Meta } from "@/lib/type/meta.type"; 5 | import { cn } from "@/lib/utils"; 6 | import Validator from "@/lib/validator"; 7 | import cx from "clsx"; 8 | import Image from "next/image"; 9 | import { useEffect, useState } from "react"; 10 | 11 | interface Props { 12 | type?: "small" | "large"; 13 | variant?: "twitter" | "facebook" | "linkedin" | "google"; 14 | className?: string; 15 | map: Meta; 16 | } 17 | 18 | /** 19 | * Component to display a link preview card. it uses the the useMeta hook to fetch the link preview data 20 | * @param {string} url - The url to preview 21 | */ 22 | 23 | export const LinkMetaCard: React.FC = ({ 24 | map, 25 | type = "small", 26 | className, 27 | variant = "facebook", 28 | }) => { 29 | // const [map, setMap] = useState({} as LinkMeta); 30 | const [image, setImage] = useState(""); 31 | const [title, setTitle] = useState(""); 32 | const [siteName, setSiteName] = useState(""); 33 | const [url, setUrl] = useState(""); 34 | const [description, setDescription] = useState(""); 35 | 36 | const { getMetaMap } = useMeta(); 37 | 38 | useEffect(() => { 39 | if (!map) return; 40 | 41 | if (map["og:image"]) { 42 | setImage(map["og:image"]); 43 | } 44 | if (map["og:title"]) { 45 | setTitle(map["og:title"]); 46 | } else if (map["title"]) { 47 | setTitle(map["title"]); 48 | } else { 49 | setTitle("No title specified"); 50 | } 51 | if (map["og:description"]) { 52 | setDescription(map["og:description"]); 53 | } else { 54 | setDescription("No description specified"); 55 | } 56 | if (map["og:site_name"]) { 57 | setSiteName(map["og:site_name"]); 58 | } 59 | if (map["url"]) { 60 | // Extract the hostname from the url 61 | const hostname = map["url"]; 62 | setUrl(hostname); 63 | } 64 | 65 | // eslint-disable-next-line react-hooks/exhaustive-deps 66 | }, [map]); 67 | 68 | if (!Validator.hasValue(map)) { 69 | return ( 70 |
81 | 88 |
89 | ); 90 | } 91 | 92 | if ( 93 | !Validator.hasValue(image) && 94 | !Validator.hasValue(title) && 95 | !Validator.hasValue(description) 96 | ) 97 | return null; 98 | 99 | return ( 100 | <> 101 |
{ 113 | window.open(url, "_blank"); 114 | }} 115 | > 116 | {image && variant !== "google" && } 117 |
123 | 128 | {title && ( 129 |

141 | {title} 142 |

143 | )} 144 | 149 | {description && ( 150 |

159 | {description.substring(0, 140)} 160 |

161 | )} 162 |
163 |
164 | 165 | ); 166 | function ImageView({ src }: { src: string }) { 167 | return ( 168 | {"Preview"} { 182 | e.currentTarget.src = 183 | "https://placehold.co/360x150/F8F8FF/A9A9A9/png?text=No+Preview"; 184 | }} 185 | /> 186 | ); 187 | } 188 | }; 189 | 190 | function Hostname({ 191 | hostname, 192 | variant, 193 | visible = true, 194 | }: { 195 | hostname: string; 196 | variant: string; 197 | visible: boolean; 198 | }) { 199 | if (!Validator.hasValue(hostname)) { 200 | return null; 201 | } 202 | return ( 203 |

216 | {hostname} 217 |

218 | ); 219 | } 220 | -------------------------------------------------------------------------------- /src/app/(component)/logo.tsx: -------------------------------------------------------------------------------- 1 | export default function Logo() { 2 | return ( 3 |
4 | 11 | 12 | 31 | 32 | 33 | 38 | 39 | 54 | 59 | 60 | 61 | 62 | 63 | 64 |
65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /src/app/(component)/og-form.tsx: -------------------------------------------------------------------------------- 1 | import { Input } from "@/components/ui/input"; 2 | import CodeBlock from "./code-block"; 3 | import { Meta } from "@/lib/type/meta.type"; 4 | import { Textarea } from "@/components/ui/textarea"; 5 | 6 | export default function OGForm({ 7 | meta, 8 | setMeta, 9 | }: { 10 | meta?: Meta; 11 | setMeta: React.Dispatch>; 12 | }) { 13 | if (!meta) return null; 14 | return ( 15 |
16 |
17 |
18 |
19 |
20 |

21 | OpenGraph 22 |

23 |

24 | Open Graph tags that are you using in your site 25 |

26 |
27 | 28 | {/* Form */} 29 |
30 |
31 |
32 |
33 | Title 34 |
35 |
36 | { 39 | setMeta({ 40 | ...meta, 41 | title: e.target.value, 42 | "og:title": e.target.value, 43 | }); 44 | }} 45 | placeholder="Title" 46 | /> 47 |
48 |
49 | {meta?.title?.length} 50 |
51 |
52 | Recommended length: 60 characters 53 |
54 |
55 |
56 |
57 |
58 |
59 | Description 60 |
61 |
62 |