perf: apply Vercel Next.js best practices (caching, image optimization, metadata)

This commit is contained in:
AyrisAI
2026-05-16 01:19:29 +03:00
parent 782e11f795
commit 312ee20320
4 changed files with 56 additions and 15 deletions

View File

@@ -1,8 +1,9 @@
'use server' 'use server'
import { cache } from 'react';
import sql from '@/lib/db'; import sql from '@/lib/db';
export async function getSettings() { // Vercel Best Practice: server-cache-react - Deduplicate data fetching per request
export const getSettings = cache(async function() {
try { try {
const settings = await sql`SELECT * FROM settings WHERE id = 1 LIMIT 1`; const settings = await sql`SELECT * FROM settings WHERE id = 1 LIMIT 1`;
return settings[0] || null; return settings[0] || null;
@@ -10,7 +11,7 @@ export async function getSettings() {
console.error('Error fetching settings:', error); console.error('Error fetching settings:', error);
return null; return null;
} }
} });
export async function getFeaturedServices() { export async function getFeaturedServices() {
try { try {
@@ -69,7 +70,7 @@ export async function getPartners() {
} }
} }
export async function getProjectBySlug(slug: string) { export const getProjectBySlug = cache(async function(slug: string) {
try { try {
const projects = await sql`SELECT * FROM projects WHERE slug = ${slug} LIMIT 1`; const projects = await sql`SELECT * FROM projects WHERE slug = ${slug} LIMIT 1`;
if (projects.length === 0) return null; if (projects.length === 0) return null;
@@ -96,8 +97,9 @@ export async function getProjectBySlug(slug: string) {
console.error('Error fetching project:', error); console.error('Error fetching project:', error);
return null; return null;
} }
} });
export async function getServiceBySlug(slug: string) {
export const getServiceBySlug = cache(async function(slug: string) {
try { try {
const services = await sql`SELECT * FROM services WHERE slug = ${slug} LIMIT 1`; const services = await sql`SELECT * FROM services WHERE slug = ${slug} LIMIT 1`;
return services[0] || null; return services[0] || null;
@@ -105,9 +107,9 @@ export async function getServiceBySlug(slug: string) {
console.error('Error fetching service:', error); console.error('Error fetching service:', error);
return null; return null;
} }
} });
export async function getLocationBySlug(slug: string) { export const getLocationBySlug = cache(async function(slug: string) {
try { try {
const locations = await sql`SELECT * FROM locations WHERE slug = ${slug} LIMIT 1`; const locations = await sql`SELECT * FROM locations WHERE slug = ${slug} LIMIT 1`;
return locations[0] || null; return locations[0] || null;
@@ -115,7 +117,7 @@ export async function getLocationBySlug(slug: string) {
console.error('Error fetching location:', error); console.error('Error fetching location:', error);
return null; return null;
} }
} });
export async function getProjectsByService(serviceName: string) { export async function getProjectsByService(serviceName: string) {
@@ -137,7 +139,8 @@ export async function getProjectsByService(serviceName: string) {
export async function getLocations() { export async function getLocations() {
try { try {
return await sql`SELECT * FROM locations ORDER BY name ASC`; const locations = await sql`SELECT * FROM locations ORDER BY name ASC`;
return locations;
} catch (error) { } catch (error) {
console.error('Error fetching locations:', error); console.error('Error fetching locations:', error);
return []; return [];
@@ -146,7 +149,8 @@ export async function getLocations() {
export async function getServices() { export async function getServices() {
try { try {
return await sql`SELECT * FROM services ORDER BY display_order ASC`; const services = await sql`SELECT * FROM services ORDER BY display_order ASC`;
return services;
} catch (error) { } catch (error) {
console.error('Error fetching services:', error); console.error('Error fetching services:', error);
return []; return [];

View File

@@ -25,12 +25,27 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
const title = `${location.name} ${service.title} | Muğla Dijital`; const title = `${location.name} ${service.title} | Muğla Dijital`;
const description = `${location.name} bölgesinde profesyonel ${service.title.toLowerCase()} hizmetleri. Muğla Dijital Medya Ajansı ile markanızı zirveye taşıyın.`; const description = `${location.name} bölgesinde profesyonel ${service.title.toLowerCase()} hizmetleri. Muğla Dijital Medya Ajansı ile markanızı zirveye taşıyın.`;
const url = `https://mugladijitalmedya.com/services/${slug}/${locationSlug}`;
return { return {
title, title,
description, description,
alternates: { alternates: {
canonical: `/services/${slug}/${locationSlug}`, canonical: url,
},
openGraph: {
title,
description,
url,
images: [
{
url: "https://mugladijitalmedya.com/og-image.jpg", // Default OG image
width: 1200,
height: 630,
alt: title,
}
],
type: 'website',
} }
}; };
} }

View File

@@ -9,14 +9,34 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str
if (!data) return {}; if (!data) return {};
const { project } = data; const { project } = data;
const url = `https://mugladijitalmedya.com/works/${slug}`;
return { return {
title: project.title,
description: project.subtitle,
openGraph: {
title: `${project.title} | Muğla Dijital`, title: `${project.title} | Muğla Dijital`,
description: project.subtitle, description: project.subtitle,
images: [project.hero_image], alternates: {
canonical: url,
}, },
openGraph: {
title: `${project.title} | Proje Detayı | Muğla Dijital`,
description: project.subtitle,
url: url,
images: [
{
url: project.hero_image,
width: 1200,
height: 630,
alt: project.title,
}
],
type: 'article',
},
twitter: {
card: 'summary_large_image',
title: project.title,
description: project.subtitle,
images: [project.hero_image],
}
}; };
} }

View File

@@ -37,6 +37,8 @@ function ProjectCard({ hero_image, category, title, year, subtitle, slug, index
src={hero_image || "https://images.unsplash.com/photo-1550745165-9bc0b252726f"} src={hero_image || "https://images.unsplash.com/photo-1550745165-9bc0b252726f"}
alt={title} alt={title}
fill fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
priority={index < 3}
className="object-cover transition-all duration-700 grayscale group-hover:grayscale-0" className="object-cover transition-all duration-700 grayscale group-hover:grayscale-0"
/> />
</motion.div> </motion.div>