mirror of
				https://github.com/walkxcode/dashboard-icons.git
				synced 2025-10-27 13:39:03 +08:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			renovate/t
			...
			feat/struc
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | ee297afbf4 | ||
|   | 40482771fa | ||
|   | 34fef44222 | ||
|   | 3e2709e7a8 | ||
|   | 245033befc | ||
|   | 9949f663eb | ||
|   | a579d41f45 | 
| @@ -22,6 +22,9 @@ | |||||||
| 			"recommended": true, | 			"recommended": true, | ||||||
| 			"suspicious": { | 			"suspicious": { | ||||||
| 				"noArrayIndexKey": "off" | 				"noArrayIndexKey": "off" | ||||||
|  | 			}, | ||||||
|  | 			"security": { | ||||||
|  | 				"noDangerouslySetInnerHtml": "off" | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								web/public/robots.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								web/public/robots.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | # Allow all user agents | ||||||
|  | User-agent: * | ||||||
|  | Allow: / | ||||||
|  |  | ||||||
|  | # Sitemap location (adjust if needed) | ||||||
|  | Sitemap: https://dashboardicons.com/sitemap.xml | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| import { readFile } from "node:fs/promises" | import { readFile } from "node:fs/promises" | ||||||
| import { join } from "node:path" | import { join } from "node:path" | ||||||
|  | import { SITE_NAME, SITE_TAGLINE, WEB_URL, getIconDescription } from "@/constants" | ||||||
| import { getAllIcons } from "@/lib/api" | import { getAllIcons } from "@/lib/api" | ||||||
| import { ImageResponse } from "next/og" | import { ImageResponse } from "next/og" | ||||||
|  |  | ||||||
| @@ -32,10 +33,9 @@ export default async function Image({ params }: { params: { icon: string } }) { | |||||||
| 	let iconData: Buffer | null = null | 	let iconData: Buffer | null = null | ||||||
| 	try { | 	try { | ||||||
| 		const iconPath = join(process.cwd(), `../png/${icon}.png`) | 		const iconPath = join(process.cwd(), `../png/${icon}.png`) | ||||||
| 		console.log(`Generating opengraph image for ${icon} (${index + 1} / ${totalIcons}) from path ${iconPath}`) |  | ||||||
| 		iconData = await readFile(iconPath) | 		iconData = await readFile(iconPath) | ||||||
| 	} catch (error) { | 	} catch (error) { | ||||||
| 		console.error(`Icon ${icon} was not found locally`) | 		// Icon file might not be found, fallback handled below | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Convert the image data to a data URL or use placeholder | 	// Convert the image data to a data URL or use placeholder | ||||||
| @@ -52,9 +52,9 @@ export default async function Image({ params }: { params: { icon: string } }) { | |||||||
| 				position: "relative", | 				position: "relative", | ||||||
| 				fontFamily: "Inter, system-ui, sans-serif", | 				fontFamily: "Inter, system-ui, sans-serif", | ||||||
| 				overflow: "hidden", | 				overflow: "hidden", | ||||||
| 				backgroundColor: "white", | 				backgroundColor: "#0f172a", // Dark background (slate-900) | ||||||
| 				backgroundImage: | 				backgroundImage: | ||||||
| 					"radial-gradient(circle at 25px 25px, lightgray 2%, transparent 0%), radial-gradient(circle at 75px 75px, lightgray 2%, transparent 0%)", | 					"radial-gradient(circle at 25px 25px, #1e293b 2%, transparent 0%), radial-gradient(circle at 75px 75px, #1e293b 2%, transparent 0%)", | ||||||
| 				backgroundSize: "100px 100px", | 				backgroundSize: "100px 100px", | ||||||
| 			}} | 			}} | ||||||
| 		> | 		> | ||||||
| @@ -67,7 +67,7 @@ export default async function Image({ params }: { params: { icon: string } }) { | |||||||
| 					width: 400, | 					width: 400, | ||||||
| 					height: 400, | 					height: 400, | ||||||
| 					borderRadius: "50%", | 					borderRadius: "50%", | ||||||
| 					background: "linear-gradient(135deg, rgba(56, 189, 248, 0.1) 0%, rgba(59, 130, 246, 0.1) 100%)", | 					background: "linear-gradient(135deg, rgba(56, 189, 248, 0.15) 0%, rgba(59, 130, 246, 0.15) 100%)", | ||||||
| 					filter: "blur(80px)", | 					filter: "blur(80px)", | ||||||
| 					zIndex: 2, | 					zIndex: 2, | ||||||
| 				}} | 				}} | ||||||
| @@ -80,7 +80,7 @@ export default async function Image({ params }: { params: { icon: string } }) { | |||||||
| 					width: 500, | 					width: 500, | ||||||
| 					height: 500, | 					height: 500, | ||||||
| 					borderRadius: "50%", | 					borderRadius: "50%", | ||||||
| 					background: "linear-gradient(135deg, rgba(249, 115, 22, 0.1) 0%, rgba(234, 88, 12, 0.1) 100%)", | 					background: "linear-gradient(135deg, rgba(249, 115, 22, 0.15) 0%, rgba(234, 88, 12, 0.15) 100%)", | ||||||
| 					filter: "blur(100px)", | 					filter: "blur(100px)", | ||||||
| 					zIndex: 2, | 					zIndex: 2, | ||||||
| 				}} | 				}} | ||||||
| @@ -109,8 +109,8 @@ export default async function Image({ params }: { params: { icon: string } }) { | |||||||
| 						width: 320, | 						width: 320, | ||||||
| 						height: 320, | 						height: 320, | ||||||
| 						borderRadius: 32, | 						borderRadius: 32, | ||||||
| 						background: "white", | 						background: "#1e293b", // Dark container (slate-800) | ||||||
| 						boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05)", | 						boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.1)", | ||||||
| 						padding: 30, | 						padding: 30, | ||||||
| 						flexShrink: 0, | 						flexShrink: 0, | ||||||
| 						position: "relative", | 						position: "relative", | ||||||
| @@ -121,7 +121,7 @@ export default async function Image({ params }: { params: { icon: string } }) { | |||||||
| 						style={{ | 						style={{ | ||||||
| 							position: "absolute", | 							position: "absolute", | ||||||
| 							inset: 0, | 							inset: 0, | ||||||
| 							background: "linear-gradient(145deg, #ffffff 0%, #f8fafc 100%)", | 							background: "linear-gradient(145deg, #1e293b 0%, #0f172a 100%)", | ||||||
| 							zIndex: 0, | 							zIndex: 0, | ||||||
| 						}} | 						}} | ||||||
| 					/> | 					/> | ||||||
| @@ -134,7 +134,7 @@ export default async function Image({ params }: { params: { icon: string } }) { | |||||||
| 							objectFit: "contain", | 							objectFit: "contain", | ||||||
| 							position: "relative", | 							position: "relative", | ||||||
| 							zIndex: 1, | 							zIndex: 1, | ||||||
| 							filter: "drop-shadow(0 10px 15px rgba(0, 0, 0, 0.1))", | 							filter: "drop-shadow(0 10px 15px rgba(0, 0, 0, 0.3))", | ||||||
| 						}} | 						}} | ||||||
| 					/> | 					/> | ||||||
| 				</div> | 				</div> | ||||||
| @@ -154,7 +154,7 @@ export default async function Image({ params }: { params: { icon: string } }) { | |||||||
| 							display: "flex", | 							display: "flex", | ||||||
| 							fontSize: 64, | 							fontSize: 64, | ||||||
| 							fontWeight: 800, | 							fontWeight: 800, | ||||||
| 							color: "#0f172a", | 							color: "#f8fafc", // Light text for dark background (slate-50) | ||||||
| 							lineHeight: 1.1, | 							lineHeight: 1.1, | ||||||
| 							letterSpacing: "-0.02em", | 							letterSpacing: "-0.02em", | ||||||
| 						}} | 						}} | ||||||
| @@ -167,14 +167,14 @@ export default async function Image({ params }: { params: { icon: string } }) { | |||||||
| 							display: "flex", | 							display: "flex", | ||||||
| 							fontSize: 32, | 							fontSize: 32, | ||||||
| 							fontWeight: 500, | 							fontWeight: 500, | ||||||
| 							color: "#64748b", | 							color: "#94a3b8", // Muted text (slate-400) | ||||||
| 							lineHeight: 1.4, | 							lineHeight: 1.4, | ||||||
| 							position: "relative", | 							position: "relative", | ||||||
| 							paddingLeft: 16, | 							paddingLeft: 16, | ||||||
| 							borderLeft: "4px solid #94a3b8", | 							borderLeft: "4px solid #64748b", // slate-500 | ||||||
| 						}} | 						}} | ||||||
| 					> | 					> | ||||||
| 						Amongst {totalIcons} other high-quality dashboard icons | 						{getIconDescription(formattedIconName, totalIcons)} | ||||||
| 					</div> | 					</div> | ||||||
|  |  | ||||||
| 					<div | 					<div | ||||||
| @@ -191,14 +191,14 @@ export default async function Image({ params }: { params: { icon: string } }) { | |||||||
| 									display: "flex", | 									display: "flex", | ||||||
| 									alignItems: "center", | 									alignItems: "center", | ||||||
| 									justifyContent: "center", | 									justifyContent: "center", | ||||||
| 									backgroundColor: "#f1f5f9", | 									backgroundColor: "#334155", // slate-700 | ||||||
| 									color: "#475569", | 									color: "#e2e8f0", // slate-200 | ||||||
| 									border: "2px solid #e2e8f0", | 									border: "2px solid #475569", // slate-600 | ||||||
| 									borderRadius: 12, | 									borderRadius: 12, | ||||||
| 									padding: "8px 16px", | 									padding: "8px 16px", | ||||||
| 									fontSize: 18, | 									fontSize: 18, | ||||||
| 									fontWeight: 600, | 									fontWeight: 600, | ||||||
| 									boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)", | 									boxShadow: "0 1px 2px rgba(0, 0, 0, 0.2)", | ||||||
| 								}} | 								}} | ||||||
| 							> | 							> | ||||||
| 								{format} | 								{format} | ||||||
| @@ -219,8 +219,8 @@ export default async function Image({ params }: { params: { icon: string } }) { | |||||||
| 					display: "flex", | 					display: "flex", | ||||||
| 					alignItems: "center", | 					alignItems: "center", | ||||||
| 					justifyContent: "center", | 					justifyContent: "center", | ||||||
| 					background: "#ffffff", | 					background: "#1e293b", // slate-800 | ||||||
| 					borderTop: "2px solid rgba(0, 0, 0, 0.05)", | 					borderTop: "2px solid rgba(255, 255, 255, 0.1)", | ||||||
| 					zIndex: 20, | 					zIndex: 20, | ||||||
| 				}} | 				}} | ||||||
| 			> | 			> | ||||||
| @@ -229,7 +229,7 @@ export default async function Image({ params }: { params: { icon: string } }) { | |||||||
| 						display: "flex", | 						display: "flex", | ||||||
| 						fontSize: 24, | 						fontSize: 24, | ||||||
| 						fontWeight: 600, | 						fontWeight: 600, | ||||||
| 						color: "#334155", | 						color: "#e2e8f0", // slate-200 | ||||||
| 						alignItems: "center", | 						alignItems: "center", | ||||||
| 						gap: 10, | 						gap: 10, | ||||||
| 					}} | 					}} | ||||||
| @@ -239,11 +239,11 @@ export default async function Image({ params }: { params: { icon: string } }) { | |||||||
| 							width: 8, | 							width: 8, | ||||||
| 							height: 8, | 							height: 8, | ||||||
| 							borderRadius: "50%", | 							borderRadius: "50%", | ||||||
| 							backgroundColor: "#3b82f6", | 							backgroundColor: "#3b82f6", // blue-500 | ||||||
| 							marginRight: 4, | 							marginRight: 4, | ||||||
| 						}} | 						}} | ||||||
| 					/> | 					/> | ||||||
| 					dashboardicons.com | 					{WEB_URL.replace("https://", "")} | ||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div>, | 		</div>, | ||||||
|   | |||||||
| @@ -1,8 +1,20 @@ | |||||||
| import { IconDetails } from "@/components/icon-details" | import { IconDetails } from "@/components/icon-details" | ||||||
| import { BASE_URL, WEB_URL } from "@/constants" | import { StructuredData } from "@/components/structured-data" | ||||||
|  | import { | ||||||
|  | 	BASE_URL, | ||||||
|  | 	GITHUB_URL, | ||||||
|  | 	ICON_DETAIL_KEYWORDS, | ||||||
|  | 	SITE_NAME, | ||||||
|  | 	SITE_TAGLINE, | ||||||
|  | 	TITLE_SEPARATOR, | ||||||
|  | 	WEB_URL, | ||||||
|  | 	getIconDescription, | ||||||
|  | 	getIconSchema, | ||||||
|  | } from "@/constants" | ||||||
| import { getAllIcons, getAuthorData } from "@/lib/api" | import { getAllIcons, getAuthorData } from "@/lib/api" | ||||||
| import type { Metadata, ResolvingMetadata } from "next" | import type { Metadata, ResolvingMetadata } from "next" | ||||||
| import { notFound } from "next/navigation" | import { notFound } from "next/navigation" | ||||||
|  | import Script from "next/script" | ||||||
|  |  | ||||||
| export const dynamicParams = false | export const dynamicParams = false | ||||||
|  |  | ||||||
| @@ -16,12 +28,12 @@ export async function generateStaticParams() { | |||||||
| export const dynamic = "force-static" | export const dynamic = "force-static" | ||||||
|  |  | ||||||
| type Props = { | type Props = { | ||||||
| 	params: Promise<{ icon: string }> | 	params: { icon: string } | ||||||
| 	searchParams: Promise<{ [key: string]: string | string[] | undefined }> | 	searchParams: { [key: string]: string | string[] | undefined } | ||||||
| } | } | ||||||
|  |  | ||||||
| export async function generateMetadata({ params, searchParams }: Props, parent: ResolvingMetadata): Promise<Metadata> { | export async function generateMetadata({ params, searchParams }: Props, parent: ResolvingMetadata): Promise<Metadata> { | ||||||
| 	const { icon } = await params | 	const { icon } = params | ||||||
| 	const iconsData = await getAllIcons() | 	const iconsData = await getAllIcons() | ||||||
| 	if (!iconsData[icon]) { | 	if (!iconsData[icon]) { | ||||||
| 		notFound() | 		notFound() | ||||||
| @@ -31,8 +43,6 @@ export async function generateMetadata({ params, searchParams }: Props, parent: | |||||||
| 	const updateDate = new Date(iconsData[icon].update.timestamp) | 	const updateDate = new Date(iconsData[icon].update.timestamp) | ||||||
| 	const totalIcons = Object.keys(iconsData).length | 	const totalIcons = Object.keys(iconsData).length | ||||||
|  |  | ||||||
| 	console.debug(`Generated metadata for ${icon} by ${authorName} (${authorData.html_url}) updated at ${updateDate.toLocaleString()}`) |  | ||||||
|  |  | ||||||
| 	const iconImageUrl = `${BASE_URL}/png/${icon}.png` | 	const iconImageUrl = `${BASE_URL}/png/${icon}.png` | ||||||
| 	const pageUrl = `${WEB_URL}/icons/${icon}` | 	const pageUrl = `${WEB_URL}/icons/${icon}` | ||||||
| 	const formattedIconName = icon | 	const formattedIconName = icon | ||||||
| @@ -40,43 +50,39 @@ export async function generateMetadata({ params, searchParams }: Props, parent: | |||||||
| 		.map((word) => word.charAt(0).toUpperCase() + word.slice(1)) | 		.map((word) => word.charAt(0).toUpperCase() + word.slice(1)) | ||||||
| 		.join(" ") | 		.join(" ") | ||||||
|  |  | ||||||
|  | 	const title = `${formattedIconName} Icon ${TITLE_SEPARATOR} ${SITE_NAME}` | ||||||
|  | 	const fullTitle = `${formattedIconName} Icon ${TITLE_SEPARATOR} ${SITE_NAME} ${TITLE_SEPARATOR} ${SITE_TAGLINE}` | ||||||
|  | 	const description = getIconDescription(formattedIconName, totalIcons) | ||||||
|  |  | ||||||
| 	return { | 	return { | ||||||
| 		title: `${formattedIconName} Icon | Dashboard Icons`, | 		title, | ||||||
| 		description: `Download the ${formattedIconName} icon in SVG, PNG, and WEBP formats for FREE. Part of a collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`, | 		description, | ||||||
| 		assets: [iconImageUrl], | 		assets: [iconImageUrl], | ||||||
| 		category: "icons", | 		category: "Icons", | ||||||
| 		keywords: [ | 		keywords: ICON_DETAIL_KEYWORDS(formattedIconName), | ||||||
| 			`${formattedIconName} icon`, |  | ||||||
| 			"dashboard icon", |  | ||||||
| 			"service icon", |  | ||||||
| 			"application icon", |  | ||||||
| 			"tool icon", |  | ||||||
| 			"web dashboard", |  | ||||||
| 			"app directory", |  | ||||||
| 		], |  | ||||||
| 		icons: { | 		icons: { | ||||||
| 			icon: iconImageUrl, | 			icon: iconImageUrl, | ||||||
| 		}, | 		}, | ||||||
| 		abstract: `Download the ${formattedIconName} icon in SVG, PNG, and WEBP formats for FREE. Part of a collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`, | 		abstract: description, | ||||||
| 		robots: { | 		robots: { | ||||||
| 			index: true, | 			index: true, | ||||||
| 			follow: true, | 			follow: true, | ||||||
| 		}, | 		}, | ||||||
| 		openGraph: { | 		openGraph: { | ||||||
| 			title: `${formattedIconName} Icon | Dashboard Icons`, | 			title: title, | ||||||
| 			description: `Download the ${formattedIconName} icon in SVG, PNG, and WEBP formats for FREE. Part of a collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`, | 			description, | ||||||
| 			type: "article", | 			type: "article", | ||||||
| 			url: pageUrl, | 			url: pageUrl, | ||||||
| 			authors: [authorName], | 			authors: [authorName], | ||||||
| 			publishedTime: updateDate.toISOString(), | 			publishedTime: updateDate.toISOString(), | ||||||
| 			modifiedTime: updateDate.toISOString(), | 			modifiedTime: updateDate.toISOString(), | ||||||
| 			section: "Icons", | 			section: "Icons", | ||||||
| 			tags: [formattedIconName, "dashboard icon", "service icon", "application icon", "tool icon", "web dashboard", "app directory"], | 			tags: [formattedIconName, ...ICON_DETAIL_KEYWORDS(formattedIconName)], | ||||||
| 		}, | 		}, | ||||||
| 		twitter: { | 		twitter: { | ||||||
| 			card: "summary_large_image", | 			card: "summary_large_image", | ||||||
| 			title: `${formattedIconName} Icon | Dashboard Icons`, | 			title: title, | ||||||
| 			description: `Download the ${formattedIconName} icon in SVG, PNG, and WEBP formats for FREE. Part of a collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`, | 			description, | ||||||
| 			images: [iconImageUrl], | 			images: [iconImageUrl], | ||||||
| 		}, | 		}, | ||||||
| 		alternates: { | 		alternates: { | ||||||
| @@ -90,8 +96,8 @@ export async function generateMetadata({ params, searchParams }: Props, parent: | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| export default async function IconPage({ params }: { params: Promise<{ icon: string }> }) { | export default async function IconPage({ params }: { params: { icon: string } }) { | ||||||
| 	const { icon } = await params | 	const { icon } = params | ||||||
| 	const iconsData = await getAllIcons() | 	const iconsData = await getAllIcons() | ||||||
| 	const originalIconData = iconsData[icon] | 	const originalIconData = iconsData[icon] | ||||||
|  |  | ||||||
| @@ -100,6 +106,26 @@ export default async function IconPage({ params }: { params: Promise<{ icon: str | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	const authorData = await getAuthorData(originalIconData.update.author.id) | 	const authorData = await getAuthorData(originalIconData.update.author.id) | ||||||
|  | 	const updateDate = new Date(originalIconData.update.timestamp) | ||||||
|  | 	const authorName = authorData.name || authorData.login | ||||||
|  | 	const formattedIconName = icon | ||||||
|  | 		.split("-") | ||||||
|  | 		.map((word) => word.charAt(0).toUpperCase() + word.slice(1)) | ||||||
|  | 		.join(" ") | ||||||
|  |  | ||||||
| 	return <IconDetails icon={icon} iconData={originalIconData} authorData={authorData} /> | 	const imageSchema = getIconSchema( | ||||||
|  | 		formattedIconName, | ||||||
|  | 		icon, | ||||||
|  | 		authorName, | ||||||
|  | 		authorData.html_url, | ||||||
|  | 		updateDate.toISOString(), | ||||||
|  | 		Object.keys(iconsData).length, | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	return ( | ||||||
|  | 		<> | ||||||
|  | 			<StructuredData data={imageSchema} id="image-schema" /> | ||||||
|  | 			<IconDetails icon={icon} iconData={originalIconData} authorData={authorData} /> | ||||||
|  | 		</> | ||||||
|  | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +1,17 @@ | |||||||
| import { BASE_URL } from "@/constants" | import { StructuredData } from "@/components/structured-data" | ||||||
|  | import { | ||||||
|  | 	BASE_URL, | ||||||
|  | 	BROWSE_KEYWORDS, | ||||||
|  | 	DEFAULT_OG_IMAGE, | ||||||
|  | 	GITHUB_URL, | ||||||
|  | 	ORGANIZATION_NAME, | ||||||
|  | 	ORGANIZATION_SCHEMA, | ||||||
|  | 	SITE_NAME, | ||||||
|  | 	SITE_TAGLINE, | ||||||
|  | 	TITLE_SEPARATOR, | ||||||
|  | 	WEB_URL, | ||||||
|  | 	getBrowseDescription, | ||||||
|  | } from "@/constants" | ||||||
| import { getIconsArray } from "@/lib/api" | import { getIconsArray } from "@/lib/api" | ||||||
| import type { Metadata } from "next" | import type { Metadata } from "next" | ||||||
| import { IconSearch } from "./components/icon-search" | import { IconSearch } from "./components/icon-search" | ||||||
| @@ -7,42 +20,28 @@ export async function generateMetadata(): Promise<Metadata> { | |||||||
| 	const icons = await getIconsArray() | 	const icons = await getIconsArray() | ||||||
| 	const totalIcons = icons.length | 	const totalIcons = icons.length | ||||||
|  |  | ||||||
|  | 	const title = `Browse Icons ${TITLE_SEPARATOR} ${SITE_NAME}` | ||||||
|  | 	const description = getBrowseDescription(totalIcons) | ||||||
|  |  | ||||||
| 	return { | 	return { | ||||||
| 		title: "Browse Icons | Free Dashboard Icons", | 		title, | ||||||
| 		description: `Search and browse through our collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`, | 		description, | ||||||
| 		keywords: [ | 		keywords: BROWSE_KEYWORDS, | ||||||
| 			"browse icons", |  | ||||||
| 			"dashboard icons", |  | ||||||
| 			"icon search", |  | ||||||
| 			"service icons", |  | ||||||
| 			"application icons", |  | ||||||
| 			"tool icons", |  | ||||||
| 			"web dashboard", |  | ||||||
| 			"app directory", |  | ||||||
| 		], |  | ||||||
| 		openGraph: { | 		openGraph: { | ||||||
| 			title: "Browse Icons | Free Dashboard Icons", | 			title: title, | ||||||
| 			description: `Search and browse through our collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`, | 			description, | ||||||
| 			type: "website", | 			type: "website", | ||||||
| 			url: `${BASE_URL}/icons`, | 			url: `${WEB_URL}/icons`, | ||||||
| 			images: [ | 			images: [DEFAULT_OG_IMAGE], | ||||||
| 				{ |  | ||||||
| 					url: "/og-image.png", |  | ||||||
| 					width: 1200, |  | ||||||
| 					height: 630, |  | ||||||
| 					alt: "Browse Dashboard Icons Collection", |  | ||||||
| 					type: "image/png", |  | ||||||
| 				}, |  | ||||||
| 			], |  | ||||||
| 		}, | 		}, | ||||||
| 		twitter: { | 		twitter: { | ||||||
| 			card: "summary_large_image", | 			card: "summary_large_image", | ||||||
| 			title: "Browse Icons | Free Dashboard Icons", | 			title: title, | ||||||
| 			description: `Search and browse through our collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`, | 			description, | ||||||
| 			images: ["/og-image-browse.png"], | 			images: [DEFAULT_OG_IMAGE.url], | ||||||
| 		}, | 		}, | ||||||
| 		alternates: { | 		alternates: { | ||||||
| 			canonical: `${BASE_URL}/icons`, | 			canonical: `${WEB_URL}/icons`, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -51,20 +50,40 @@ export const dynamic = "force-static" | |||||||
|  |  | ||||||
| export default async function IconsPage() { | export default async function IconsPage() { | ||||||
| 	const icons = await getIconsArray() | 	const icons = await getIconsArray() | ||||||
| 	return ( |  | ||||||
| 		<div className="isolate overflow-hidden"> |  | ||||||
| 			<div className="py-8"> |  | ||||||
| 				<div className="space-y-4 mb-8 mx-auto max-w-7xl"> |  | ||||||
| 					<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4"> |  | ||||||
| 						<div> |  | ||||||
| 							<h1 className="text-3xl font-bold">Browse icons</h1> |  | ||||||
| 							<p className="text-muted-foreground">Search through our collection of {icons.length} beautiful icons.</p> |  | ||||||
| 						</div> |  | ||||||
| 					</div> |  | ||||||
|  |  | ||||||
| 					<IconSearch icons={icons} /> | 	const gallerySchema = { | ||||||
|  | 		"@context": "https://schema.org", | ||||||
|  | 		"@type": "ImageGallery", | ||||||
|  | 		name: `${SITE_NAME} - Browse ${icons.length} Icons - ${SITE_TAGLINE}`, | ||||||
|  | 		description: getBrowseDescription(icons.length), | ||||||
|  | 		url: `${WEB_URL}/icons`, | ||||||
|  | 		numberOfItems: icons.length, | ||||||
|  | 		creator: { | ||||||
|  | 			"@type": "Organization", | ||||||
|  | 			name: ORGANIZATION_NAME, | ||||||
|  | 			url: GITHUB_URL, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ( | ||||||
|  | 		<> | ||||||
|  | 			<StructuredData data={gallerySchema} id="gallery-schema" /> | ||||||
|  | 			<div className="isolate overflow-hidden"> | ||||||
|  | 				<div className="py-8"> | ||||||
|  | 					<div className="space-y-4 mb-8 mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> | ||||||
|  | 						<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4"> | ||||||
|  | 							<div> | ||||||
|  | 								<h1 className="text-3xl font-bold">Icons</h1> | ||||||
|  | 								<p className="text-muted-foreground"> | ||||||
|  | 									Search our collection of {icons.length} icons - {SITE_TAGLINE}. | ||||||
|  | 								</p> | ||||||
|  | 							</div> | ||||||
|  | 						</div> | ||||||
|  |  | ||||||
|  | 						<IconSearch icons={icons} /> | ||||||
|  | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</> | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,12 +2,26 @@ import { PostHogProvider } from "@/components/PostHogProvider" | |||||||
| import { Footer } from "@/components/footer" | import { Footer } from "@/components/footer" | ||||||
| import { HeaderWrapper } from "@/components/header-wrapper" | import { HeaderWrapper } from "@/components/header-wrapper" | ||||||
| import { LicenseNotice } from "@/components/license-notice" | import { LicenseNotice } from "@/components/license-notice" | ||||||
|  | import { WebsiteStructuredData } from "@/components/structured-data" | ||||||
| import { getTotalIcons } from "@/lib/api" | import { getTotalIcons } from "@/lib/api" | ||||||
| import type { Metadata, Viewport } from "next" | import type { Metadata, Viewport } from "next" | ||||||
| import { Inter } from "next/font/google" | import { Inter } from "next/font/google" | ||||||
| import { Toaster } from "sonner" | import { Toaster } from "sonner" | ||||||
| import "./globals.css" | import "./globals.css" | ||||||
| import { getDescription, websiteTitle } from "@/constants" | import { | ||||||
|  | 	DEFAULT_KEYWORDS, | ||||||
|  | 	DEFAULT_OG_IMAGE, | ||||||
|  | 	GITHUB_URL, | ||||||
|  | 	ORGANIZATION_NAME, | ||||||
|  | 	ORGANIZATION_SCHEMA, | ||||||
|  | 	SITE_NAME, | ||||||
|  | 	SITE_TAGLINE, | ||||||
|  | 	WEB_URL, | ||||||
|  | 	getDescription, | ||||||
|  | 	getWebsiteSchema, | ||||||
|  | 	websiteFullTitle, | ||||||
|  | 	websiteTitle, | ||||||
|  | } from "@/constants" | ||||||
| import { ThemeProvider } from "./theme-provider" | import { ThemeProvider } from "./theme-provider" | ||||||
|  |  | ||||||
| const inter = Inter({ | const inter = Inter({ | ||||||
| @@ -27,12 +41,16 @@ export const viewport: Viewport = { | |||||||
|  |  | ||||||
| export async function generateMetadata(): Promise<Metadata> { | export async function generateMetadata(): Promise<Metadata> { | ||||||
| 	const { totalIcons } = await getTotalIcons() | 	const { totalIcons } = await getTotalIcons() | ||||||
|  | 	const description = getDescription(totalIcons) | ||||||
|  |  | ||||||
| 	return { | 	return { | ||||||
| 		metadataBase: new URL("https://dashboardicons.com"), | 		metadataBase: new URL(WEB_URL), | ||||||
| 		title: websiteTitle, | 		title: { | ||||||
| 		description: getDescription(totalIcons), | 			default: websiteTitle, | ||||||
| 		keywords: ["dashboard icons", "service icons", "application icons", "tool icons", "web dashboard", "app directory"], | 			template: `%s | ${websiteTitle}`, | ||||||
|  | 		}, | ||||||
|  | 		description, | ||||||
|  | 		keywords: DEFAULT_KEYWORDS, | ||||||
| 		robots: { | 		robots: { | ||||||
| 			index: true, | 			index: true, | ||||||
| 			follow: true, | 			follow: true, | ||||||
| @@ -42,33 +60,23 @@ export async function generateMetadata(): Promise<Metadata> { | |||||||
| 			googleBot: "index, follow", | 			googleBot: "index, follow", | ||||||
| 		}, | 		}, | ||||||
| 		openGraph: { | 		openGraph: { | ||||||
| 			siteName: "Dashboard Icons", | 			siteName: SITE_NAME, | ||||||
| 			type: "website", | 			type: "website", | ||||||
| 			locale: "en_US", | 			locale: "en_US", | ||||||
| 			title: websiteTitle, | 			title: websiteFullTitle, | ||||||
| 			description: getDescription(totalIcons), | 			description, | ||||||
| 			url: "https://dashboardicons.com", | 			url: WEB_URL, | ||||||
| 			images: [ | 			images: [DEFAULT_OG_IMAGE], | ||||||
| 				{ |  | ||||||
| 					url: "/og-image.png", |  | ||||||
| 					width: 1200, |  | ||||||
| 					height: 630, |  | ||||||
| 					alt: "Dashboard Icons", |  | ||||||
| 					type: "image/png", |  | ||||||
| 				}, |  | ||||||
| 			], |  | ||||||
| 		}, | 		}, | ||||||
| 		twitter: { | 		twitter: { | ||||||
| 			card: "summary_large_image", | 			card: "summary_large_image", | ||||||
| 			site: "@homarr_app", | 			title: websiteFullTitle, | ||||||
| 			creator: "@homarr_app", | 			description, | ||||||
| 			title: websiteTitle, | 			images: [DEFAULT_OG_IMAGE.url], | ||||||
| 			description: getDescription(totalIcons), |  | ||||||
| 			images: ["/og-image.png"], |  | ||||||
| 		}, | 		}, | ||||||
| 		applicationName: "Dashboard Icons", | 		applicationName: SITE_NAME, | ||||||
| 		appleWebApp: { | 		appleWebApp: { | ||||||
| 			title: "Dashboard Icons", | 			title: SITE_NAME, | ||||||
| 			statusBarStyle: "default", | 			statusBarStyle: "default", | ||||||
| 			capable: true, | 			capable: true, | ||||||
| 		}, | 		}, | ||||||
| @@ -88,14 +96,26 @@ export async function generateMetadata(): Promise<Metadata> { | |||||||
| 			], | 			], | ||||||
| 		}, | 		}, | ||||||
| 		manifest: "/site.webmanifest", | 		manifest: "/site.webmanifest", | ||||||
|  | 		authors: [{ name: ORGANIZATION_NAME, url: GITHUB_URL }], | ||||||
|  | 		creator: ORGANIZATION_NAME, | ||||||
|  | 		publisher: ORGANIZATION_NAME, | ||||||
|  | 		category: "Icons", | ||||||
|  | 		classification: "Dashboard Design Resources", | ||||||
|  | 		other: { | ||||||
|  | 			"revisit-after": "7 days", | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { | export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { | ||||||
|  | 	const { totalIcons } = await getTotalIcons() | ||||||
|  | 	const websiteSchema = getWebsiteSchema(totalIcons) | ||||||
|  |  | ||||||
| 	return ( | 	return ( | ||||||
| 		<html lang="en" suppressHydrationWarning> | 		<html lang="en" suppressHydrationWarning> | ||||||
| 			<body className={`${inter.variable} antialiased bg-background flex flex-col min-h-screen`}> | 			<body className={`${inter.variable} antialiased bg-background flex flex-col min-h-screen`}> | ||||||
| 				<PostHogProvider> | 				<PostHogProvider> | ||||||
|  | 					<WebsiteStructuredData websiteSchema={websiteSchema} organizationSchema={ORGANIZATION_SCHEMA} /> | ||||||
| 					<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange> | 					<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange> | ||||||
| 						<HeaderWrapper /> | 						<HeaderWrapper /> | ||||||
| 						<main className="flex-grow">{children}</main> | 						<main className="flex-grow">{children}</main> | ||||||
|   | |||||||
| @@ -1,42 +1,50 @@ | |||||||
| import { HeroSection } from "@/components/hero" | import { HeroSection } from "@/components/hero" | ||||||
| import { RecentlyAddedIcons } from "@/components/recently-added-icons" | import { RecentlyAddedIcons } from "@/components/recently-added-icons" | ||||||
| import { BASE_URL, REPO_NAME, getDescription, websiteTitle } from "@/constants" | import { | ||||||
|  | 	BASE_URL, | ||||||
|  | 	DEFAULT_KEYWORDS, | ||||||
|  | 	DEFAULT_OG_IMAGE, | ||||||
|  | 	GITHUB_URL, | ||||||
|  | 	ORGANIZATION_NAME, | ||||||
|  | 	ORGANIZATION_SCHEMA, | ||||||
|  | 	REPO_NAME, | ||||||
|  | 	SITE_NAME, | ||||||
|  | 	SITE_TAGLINE, | ||||||
|  | 	WEB_URL, | ||||||
|  | 	getHomeDescription, | ||||||
|  | 	websiteFullTitle, | ||||||
|  | 	websiteTitle, | ||||||
|  | } from "@/constants" | ||||||
| import { getRecentlyAddedIcons, getTotalIcons } from "@/lib/api" | import { getRecentlyAddedIcons, getTotalIcons } from "@/lib/api" | ||||||
| import type { Metadata } from "next" | import type { Metadata } from "next" | ||||||
|  |  | ||||||
| export async function generateMetadata(): Promise<Metadata> { | export async function generateMetadata(): Promise<Metadata> { | ||||||
| 	const { totalIcons } = await getTotalIcons() | 	const { totalIcons } = await getTotalIcons() | ||||||
|  | 	const description = getHomeDescription(totalIcons) | ||||||
|  |  | ||||||
| 	return { | 	return { | ||||||
| 		title: websiteTitle, | 		title: websiteTitle, | ||||||
| 		description: getDescription(totalIcons), | 		description, | ||||||
| 		keywords: ["dashboard icons", "service icons", "application icons", "tool icons", "web dashboard", "app directory"], | 		keywords: DEFAULT_KEYWORDS, | ||||||
| 		robots: { | 		robots: { | ||||||
| 			index: true, | 			index: true, | ||||||
| 			follow: true, | 			follow: true, | ||||||
| 		}, | 		}, | ||||||
| 		openGraph: { | 		openGraph: { | ||||||
| 			title: websiteTitle, | 			title: websiteFullTitle, | ||||||
| 			description: getDescription(totalIcons), | 			description, | ||||||
| 			type: "website", | 			type: "website", | ||||||
| 			url: BASE_URL, | 			url: WEB_URL, | ||||||
| 			images: [ | 			images: [DEFAULT_OG_IMAGE], | ||||||
| 				{ |  | ||||||
| 					url: "/og-image.png", |  | ||||||
| 					width: 1200, |  | ||||||
| 					height: 630, |  | ||||||
| 					alt: "Dashboard Icons", |  | ||||||
| 				}, |  | ||||||
| 			], |  | ||||||
| 		}, | 		}, | ||||||
| 		twitter: { | 		twitter: { | ||||||
| 			title: websiteTitle, | 			title: websiteFullTitle, | ||||||
| 			description: getDescription(totalIcons), | 			description, | ||||||
| 			card: "summary_large_image", | 			card: "summary_large_image", | ||||||
| 			images: ["/og-image.png"], | 			images: [DEFAULT_OG_IMAGE.url], | ||||||
| 		}, | 		}, | ||||||
| 		alternates: { | 		alternates: { | ||||||
| 			canonical: BASE_URL, | 			canonical: WEB_URL, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -44,7 +52,7 @@ export async function generateMetadata(): Promise<Metadata> { | |||||||
| async function getGitHubStars() { | async function getGitHubStars() { | ||||||
| 	const response = await fetch(`https://api.github.com/repos/${REPO_NAME}`) | 	const response = await fetch(`https://api.github.com/repos/${REPO_NAME}`) | ||||||
| 	const data = await response.json() | 	const data = await response.json() | ||||||
| 	console.log(`GitHub stars: ${data.stargazers_count}`) | 	// TODO: Consider caching this result or fetching at build time to avoid rate limits. | ||||||
| 	return data.stargazers_count | 	return data.stargazers_count | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -54,9 +62,11 @@ export default async function Home() { | |||||||
| 	const stars = await getGitHubStars() | 	const stars = await getGitHubStars() | ||||||
|  |  | ||||||
| 	return ( | 	return ( | ||||||
| 		<div className="flex flex-col min-h-screen"> | 		<> | ||||||
| 			<HeroSection totalIcons={totalIcons} stars={stars} /> | 			<div className="flex flex-col min-h-screen"> | ||||||
| 			<RecentlyAddedIcons icons={recentIcons} /> | 				<HeroSection totalIcons={totalIcons} stars={stars} /> | ||||||
| 		</div> | 				<RecentlyAddedIcons icons={recentIcons} /> | ||||||
|  | 			</div> | ||||||
|  | 		</> | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,3 +0,0 @@ | |||||||
| User-Agent: * |  | ||||||
| Allow: / |  | ||||||
| Sitemap: https://dashboardicons.com/sitemap.xml |  | ||||||
							
								
								
									
										22
									
								
								web/src/components/structured-data.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								web/src/components/structured-data.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | type StructuredDataProps = { | ||||||
|  | 	data: Record<string, unknown> | ||||||
|  | 	id?: string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export const StructuredData = ({ data, id }: StructuredDataProps) => { | ||||||
|  | 	return <script id={id} type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }} /> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type WebsiteStructuredDataProps = { | ||||||
|  | 	websiteSchema: Record<string, unknown> | ||||||
|  | 	organizationSchema: Record<string, unknown> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export const WebsiteStructuredData = ({ websiteSchema, organizationSchema }: WebsiteStructuredDataProps) => { | ||||||
|  | 	return ( | ||||||
|  | 		<> | ||||||
|  | 			<StructuredData data={websiteSchema} id="website-schema" /> | ||||||
|  | 			<StructuredData data={organizationSchema} id="organization-schema" /> | ||||||
|  | 		</> | ||||||
|  | 	) | ||||||
|  | } | ||||||
| @@ -4,7 +4,126 @@ export const METADATA_URL = "https://raw.githubusercontent.com/homarr-labs/dashb | |||||||
| export const WEB_URL = "https://dashboardicons.com" | export const WEB_URL = "https://dashboardicons.com" | ||||||
| export const REPO_NAME = "homarr-labs/dashboard-icons" | export const REPO_NAME = "homarr-labs/dashboard-icons" | ||||||
|  |  | ||||||
| export const getDescription = (totalIcons: number) => | // Site-wide metadata constants | ||||||
| 	`A collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.` | export const SITE_NAME = "Dashboard Icons" | ||||||
|  | export const TITLE_SEPARATOR = " | " | ||||||
|  | export const SITE_TAGLINE = "Your definitive source for dashboard icons" | ||||||
|  | export const ORGANIZATION_NAME = "Homarr Labs" | ||||||
|  |  | ||||||
| export const websiteTitle = "Free Dashboard Icons - Download High-Quality UI & App Icons" | export const getDescription = (totalIcons: number) => | ||||||
|  | 	`A curated collection of ${totalIcons} free icons for dashboards and app directories. Available in SVG, PNG, and WEBP formats. ${SITE_TAGLINE}.` | ||||||
|  |  | ||||||
|  | export const getHomeDescription = (totalIcons: number) => | ||||||
|  | 	`Discover our curated collection of ${totalIcons} icons designed specifically for dashboards and app directories. ${SITE_TAGLINE}.` | ||||||
|  |  | ||||||
|  | export const getBrowseDescription = (totalIcons: number) => | ||||||
|  | 	`Browse, search and download from our collection of ${totalIcons} curated icons. All icons available in SVG, PNG, and WEBP formats. ${SITE_TAGLINE}.` | ||||||
|  |  | ||||||
|  | export const getIconDescription = (iconName: string, totalIcons: number) => | ||||||
|  | 	`Download the ${iconName} icon in SVG, PNG, and WEBP formats. Part of our curated collection of ${totalIcons} free icons for dashboards. ${SITE_TAGLINE}.` | ||||||
|  |  | ||||||
|  | export const websiteTitle = `${SITE_NAME} ${TITLE_SEPARATOR} Free, Curated Icons for Apps & Services` | ||||||
|  | export const websiteFullTitle = `${SITE_NAME} ${TITLE_SEPARATOR} Free, Curated Icons for Apps & Services ${TITLE_SEPARATOR} ${SITE_TAGLINE}` | ||||||
|  |  | ||||||
|  | // Various keyword sets for different pages | ||||||
|  | export const DEFAULT_KEYWORDS = [ | ||||||
|  | 	"dashboard icons", | ||||||
|  | 	"app icons", | ||||||
|  | 	"service icons", | ||||||
|  | 	"curated icons", | ||||||
|  | 	"free icons", | ||||||
|  | 	"SVG icons", | ||||||
|  | 	"web dashboard", | ||||||
|  | 	"app directory", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | export const BROWSE_KEYWORDS = [ | ||||||
|  | 	"browse icons", | ||||||
|  | 	"search icons", | ||||||
|  | 	"download icons", | ||||||
|  | 	"minimal icons", | ||||||
|  | 	"dashboard design", | ||||||
|  | 	"UI icons", | ||||||
|  | 	...DEFAULT_KEYWORDS, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | // Add format-specific keywords | ||||||
|  | export const ICON_DETAIL_KEYWORDS = (iconName: string): string[] => [ | ||||||
|  | 	`${iconName} icon`, // e.g., "Homarr icon" | ||||||
|  | 	`${iconName} logo`, // e.g., "Homarr logo" | ||||||
|  | 	`${iconName} svg icon`, // e.g., "Homarr svg icon" | ||||||
|  | 	`${iconName} png icon`, // e.g., "Homarr png icon" | ||||||
|  | 	`${iconName} webp icon`, // e.g., "Homarr webp icon" | ||||||
|  | 	`${iconName} download`, // e.g., "Homarr download" | ||||||
|  | 	`${iconName} dashboard icon`, // e.g., "Homarr dashboard icon" | ||||||
|  | 	...DEFAULT_KEYWORDS, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | // Core structured data for the website (JSON-LD) | ||||||
|  | export const getWebsiteSchema = (totalIcons: number) => ({ | ||||||
|  | 	"@context": "https://schema.org", | ||||||
|  | 	"@type": "WebSite", | ||||||
|  | 	name: SITE_NAME, | ||||||
|  | 	url: WEB_URL, | ||||||
|  | 	description: getDescription(totalIcons), | ||||||
|  | 	potentialAction: { | ||||||
|  | 		"@type": "SearchAction", | ||||||
|  | 		target: { | ||||||
|  | 			"@type": "EntryPoint", | ||||||
|  | 			urlTemplate: `${WEB_URL}/icons?q={search_term_string}`, | ||||||
|  | 		}, | ||||||
|  | 		"query-input": "required name=search_term_string", | ||||||
|  | 	}, | ||||||
|  | 	slogan: SITE_TAGLINE, | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | // Organization schema | ||||||
|  | export const ORGANIZATION_SCHEMA = { | ||||||
|  | 	"@context": "https://schema.org", | ||||||
|  | 	"@type": "Organization", | ||||||
|  | 	name: ORGANIZATION_NAME, | ||||||
|  | 	url: `https://github.com/${REPO_NAME}`, | ||||||
|  | 	logo: `${WEB_URL}/og-image.png`, | ||||||
|  | 	sameAs: [`https://github.com/${REPO_NAME}`, "https://homarr.dev"], | ||||||
|  | 	slogan: SITE_TAGLINE, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Social media | ||||||
|  | export const GITHUB_URL = `https://github.com/${REPO_NAME}` | ||||||
|  |  | ||||||
|  | // Image schemas | ||||||
|  | export const getIconSchema = ( | ||||||
|  | 	iconName: string, | ||||||
|  | 	iconId: string, | ||||||
|  | 	authorName: string, | ||||||
|  | 	authorUrl: string, | ||||||
|  | 	updateDate: string, | ||||||
|  | 	totalIcons: number, | ||||||
|  | ) => ({ | ||||||
|  | 	"@context": "https://schema.org", | ||||||
|  | 	"@type": "ImageObject", | ||||||
|  | 	name: `${iconName} Icon`, | ||||||
|  | 	description: getIconDescription(iconName, totalIcons), | ||||||
|  | 	contentUrl: `${BASE_URL}/png/${iconId}.png`, | ||||||
|  | 	thumbnailUrl: `${BASE_URL}/png/${iconId}.png`, | ||||||
|  | 	uploadDate: updateDate, | ||||||
|  | 	author: { | ||||||
|  | 		"@type": "Person", | ||||||
|  | 		name: authorName, | ||||||
|  | 		url: authorUrl, | ||||||
|  | 	}, | ||||||
|  | 	encodingFormat: ["image/png", "image/svg+xml", "image/webp"], | ||||||
|  | 	contentSize: "Variable", | ||||||
|  | 	representativeOfPage: true, | ||||||
|  | 	creditText: `Icon contributed by ${authorName} to the ${SITE_NAME} collection by ${ORGANIZATION_NAME}`, | ||||||
|  | 	embedUrl: `${WEB_URL}/icons/${iconId}`, | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | // OpenGraph defaults | ||||||
|  | export const DEFAULT_OG_IMAGE = { | ||||||
|  | 	url: "/og-image.png", | ||||||
|  | 	width: 1200, | ||||||
|  | 	height: 630, | ||||||
|  | 	alt: `${SITE_NAME} - ${SITE_TAGLINE}`, | ||||||
|  | 	type: "image/png", | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user