feat: setup dynamic document metadata bindings for SEO SPA crawling
This commit is contained in:
parent
f7052ee539
commit
089662d1f3
3 changed files with 36 additions and 15 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { useNavigate } from 'react-router-dom'
|
||||
import { useEffect } from 'react'
|
||||
import { useNavigate, Link } from 'react-router-dom'
|
||||
import { motion } from 'motion/react'
|
||||
import { ArrowRight } from 'lucide-react'
|
||||
import ProductCard from '../components/ProductCard'
|
||||
|
|
@ -59,6 +60,14 @@ const cardVariant = {
|
|||
export default function HomePage() {
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
document.title = "Windows ISO Downloader | Official Microsoft Images"
|
||||
const metaDesc = document.querySelector('meta[name="description"]')
|
||||
if (metaDesc) {
|
||||
metaDesc.setAttribute('content', "Download official Microsoft Windows ISO files. Fast, free, no registration required.")
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto px-5 pt-20 pb-16 relative" style={{ overflowX: 'hidden' }}>
|
||||
|
||||
|
|
@ -79,7 +88,7 @@ export default function HomePage() {
|
|||
className="font-bold text-white tracking-tight leading-tight mb-4"
|
||||
style={{ fontSize: 'clamp(1.6rem, 7.5vw, 3.75rem)' }}
|
||||
>
|
||||
Windows ISO <span className="text-white/25">Downloader</span>
|
||||
Windows ISO <span className="text-white/50">Downloader</span>
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-base max-w-sm mx-auto mb-6 leading-relaxed">
|
||||
Direct links from Microsoft's CDN. No ads. No registration.
|
||||
|
|
@ -116,10 +125,11 @@ export default function HomePage() {
|
|||
>
|
||||
{featured.map(product => (
|
||||
<motion.div key={product.id} variants={cardVariant}>
|
||||
<ProductCard
|
||||
{...product}
|
||||
onClick={() => navigate(`/product/${product.id}`)}
|
||||
/>
|
||||
<Link to={`/product/${product.id}`} className="block outline-none">
|
||||
<ProductCard
|
||||
{...product}
|
||||
/>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
|
@ -131,13 +141,13 @@ export default function HomePage() {
|
|||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
>
|
||||
<button
|
||||
onClick={() => navigate('/products')}
|
||||
<Link
|
||||
to="/products"
|
||||
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl border border-white/8 bg-white/4 text-sm text-zinc-400 hover:text-white hover:border-white/15 hover:bg-white/7 transition-all duration-200"
|
||||
>
|
||||
Browse all {featured.length > 0 ? '17' : ''} releases
|
||||
Browse all {featured.length > 0 ? '16' : ''} releases
|
||||
<ArrowRight size={14} />
|
||||
</button>
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
{/* How it works */}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,14 @@ export default function ProductDetailPage() {
|
|||
.then((data: Record<string, string>) => {
|
||||
const name = data[productId!] ?? `Product ${productId}`
|
||||
setProductName(name)
|
||||
|
||||
// Dynamic SEO injection
|
||||
document.title = `${name} ISO Download | Windows ISO Downloader`
|
||||
const descMeta = document.querySelector('meta[name="description"]')
|
||||
if (descMeta) {
|
||||
descMeta.setAttribute('content', `Download official ISO image for ${name}. Direct links pulled securely from Microsoft CDN servers.`)
|
||||
}
|
||||
|
||||
// Extract build from parentheses e.g. "(26200.6584)"
|
||||
const match = name.match(/\(([^)]+)\)/)
|
||||
if (match) setBuildStr(match[1])
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useNavigate, Link } from 'react-router-dom'
|
||||
import { motion } from 'motion/react'
|
||||
import { Search, ArrowRight } from 'lucide-react'
|
||||
import type { Product } from '../types'
|
||||
|
|
@ -16,8 +16,11 @@ export default function ProductsPage() {
|
|||
const [products, setProducts] = useState<Product[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [query, setQuery] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
document.title = "All Windows Releases | Windows ISO Downloader"
|
||||
const metaDesc = document.querySelector('meta[name="description"]')
|
||||
if (metaDesc) metaDesc.setAttribute('content', "Browse all official Microsoft Windows ISO releases available for direct download.")
|
||||
|
||||
fetch('/data/products.json')
|
||||
.then(r => r.json())
|
||||
.then((data: Record<string, string>) => {
|
||||
|
|
@ -99,8 +102,8 @@ export default function ProductsPage() {
|
|||
show: { opacity: 1, x: 0, transition: { type: 'spring', stiffness: 300, damping: 28 } },
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={() => navigate(`/product/${product.id}`)}
|
||||
<Link
|
||||
to={`/product/${product.id}`}
|
||||
className="group w-full flex items-center justify-between px-4 py-3.5 rounded-xl border border-white/6 bg-white/3 hover:bg-white/6 hover:border-white/12 transition-all text-left"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
|
|
@ -112,7 +115,7 @@ export default function ProductsPage() {
|
|||
</span>
|
||||
</div>
|
||||
<ArrowRight size={14} className="text-white/20 group-hover:text-white/50 group-hover:translate-x-0.5 transition-all" />
|
||||
</button>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue