feat: SEO cleanup, A11y, Next.js 16 dynamic routes, hide unverified content

This commit is contained in:
MichalC
2026-08-01 13:01:08 +02:00
parent 88cb4c516d
commit f07f2c2407
15 changed files with 59 additions and 398 deletions
+2 -2
View File
@@ -8,9 +8,9 @@ import { Calendar, User, ArrowLeft, ArrowRight } from 'lucide-react';
import { companyDetails } from '@/content/company';
interface BlogPostPageProps {
params: {
params: Promise<{
slug: string;
};
}>;
}
// Generowanie ścieżek statycznych dla wpisów
+17 -3
View File
@@ -11,9 +11,9 @@
--color-foreground: var(--foreground);
--color-text: var(--foreground);
--color-text-muted: var(--muted-foreground);
--font-sans: var(--font-sans);
--font-mono: var(--font-geist-mono);
--font-heading: var(--font-heading);
--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--font-heading: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
@@ -158,6 +158,20 @@
@apply border-border outline-ring/50;
}
*:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
*, ::before, ::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
body {
@apply bg-background text-foreground overflow-x-hidden;
}
@@ -28,6 +28,10 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
return {
title: district.seoTitle,
description: district.seoDescription,
robots: {
index: false,
follow: false,
},
alternates: { canonical: `/klimatyzacja/wroclaw/${district.slug}` },
openGraph: {
title: district.seoTitle,
+6 -14
View File
@@ -1,5 +1,5 @@
import type { Metadata } from "next";
import { Inter, Poppins } from "next/font/google";
import { Header } from "@/components/layout/Header";
import { TrustBar } from "@/components/layout/TrustBar";
import { Footer } from "@/components/layout/Footer";
@@ -10,16 +10,7 @@ import { AnalyticsWrapper } from "@/components/ui/AnalyticsWrapper";
import { CookieSettingsFloatingIcon } from "@/components/ui/CookieSettingsFloatingIcon";
import "./globals.css";
const fontSans = Inter({
subsets: ["latin"],
variable: "--font-sans",
});
const fontHeading = Poppins({
subsets: ["latin"],
weight: ["600", "700", "800"],
variable: "--font-heading",
});
export const metadata: Metadata = {
title: {
@@ -61,13 +52,14 @@ export default function RootLayout({
return (
<html
lang="pl"
className={`${fontSans.variable} ${fontHeading.variable} antialiased`}
className="antialiased"
>
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
</head>
<body className="min-h-screen flex flex-col bg-background text-foreground">
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-[9999] focus:px-4 focus:py-2 focus:bg-accent focus:text-white focus:rounded-md focus:font-bold">
Przejdź do głównej treści
</a>
{process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY && (
<Script
src={`https://www.google.com/recaptcha/api.js?render=${process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}`}
@@ -75,7 +67,7 @@ export default function RootLayout({
/>
)}
<Header />
<main className="flex-grow">{children}</main>
<main id="main-content" tabIndex={-1} className="flex-grow focus:outline-none">{children}</main>
<TrustBar />
<Footer />
<AnalyticsWrapper />
+3 -6
View File
@@ -1,10 +1,7 @@
import { Metadata } from 'next';
import { HeroSection } from '@/components/sections/HeroSection';
import { AboutStory } from '@/components/sections/AboutStory';
import { StatsBar } from '@/components/sections/StatsBar';
import { ValuesGrid } from '@/components/sections/ValuesGrid';
import { CertificatesSection } from '@/components/sections/CertificatesSection';
import { Testimonials } from '@/components/sections/Testimonials';
import { CtaBanner } from '@/components/sections/CtaBanner';
import { companyDetails } from '@/content/company';
@@ -32,13 +29,13 @@ export default function ONasPage() {
<AboutStory />
<StatsBar />
{/* <StatsBar /> (Ukryte na czas zbierania autentycznych danych statystycznych) */}
<ValuesGrid />
<CertificatesSection />
{/* <CertificatesSection /> (Ukryte na czas uzyskania skanów dokumentów) */}
<Testimonials />
{/* <Testimonials /> (Ukryte na czas zbierania autentycznych opinii) */}
<CtaBanner
title="Szukasz sprawdzonej ekipy do swojego projektu?"
+4 -7
View File
@@ -3,13 +3,13 @@ import { AdvantagesBar } from "@/components/sections/AdvantagesBar";
import { ServicesGrid } from "@/components/sections/ServicesGrid";
import { WhyUs } from "@/components/sections/WhyUs";
import { FeaturedProducts } from "@/components/sections/FeaturedProducts";
import { Testimonials } from "@/components/sections/Testimonials";
import { BlogPreview } from "@/components/sections/BlogPreview";
import { CtaBanner } from "@/components/sections/CtaBanner";
import { companyDetails } from "@/content/company";
import { Wrench, Phone, ArrowRight, Thermometer, Wind, Zap, Calendar, Check } from "lucide-react";
import Link from "next/link";
import { Button, buttonClasses } from "@/components/ui/Button";
import { JsonLd } from "@/components/ui/JsonLd";
export default function Home() {
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'https://thermcool.pl';
@@ -104,7 +104,7 @@ export default function Home() {
companyDetails.socials.facebook,
companyDetails.socials.instagram,
companyDetails.socials.google,
],
].filter(Boolean),
},
{
"@type": "WebSite",
@@ -129,10 +129,7 @@ export default function Home() {
return (
<>
{/* LocalBusiness Schema for Google Search SEO */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<JsonLd data={jsonLd} />
<HeroSection />
<AdvantagesBar />
@@ -141,7 +138,7 @@ export default function Home() {
<FeaturedProducts />
<WhyUs />
{/* ProjectsSection will go here in Group B */}
<Testimonials />
{/* <Testimonials /> (Ukryte na czas zbierania autentycznych opinii) */}
{/* Serwis LG */}
<section className="py-16 bg-white">
+4
View File
@@ -9,6 +9,10 @@ export const metadata: Metadata = {
description: 'Zobacz nasze wybrane realizacje z zakresu klimatyzacji, pomp ciepła, remontów i usług hydraulicznych.',
alternates: {
canonical: '/realizacje',
},
robots: {
index: false,
follow: false,
}
};
+1 -12
View File
@@ -2,7 +2,6 @@ import { MetadataRoute } from 'next';
import { getProducts } from '@/content/products';
import { getPosts } from '@/content/posts';
import { dolnySlaskCities } from '@/content/cities';
import { wroclawDistricts } from '@/content/wroclaw-districts';
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://thermcool.pl';
@@ -11,8 +10,6 @@ export default function sitemap(): MetadataRoute.Sitemap {
'',
'/klimatyzacja',
'/pompy-ciepla',
'/realizacje',
'/blog',
'/o-nas',
'/kalkulator',
@@ -68,13 +65,5 @@ export default function sitemap(): MetadataRoute.Sitemap {
priority: 0.7,
}));
// Dynamiczne strony dzielnic
const districtUrls = wroclawDistricts.map((d) => ({
url: `${baseUrl}/klimatyzacja/wroclaw/${d.slug}`,
lastModified: new Date().toISOString(),
changeFrequency: 'monthly' as const,
priority: 0.7,
}));
return [...staticRoutes, ...productRoutes, ...postRoutes, ...cityRoutes, ...districtUrls];
return [...staticRoutes, ...productRoutes, ...postRoutes, ...cityRoutes];
}
+2 -5
View File
@@ -4,6 +4,7 @@ import { getProductBySlug, getProducts } from '@/content/products';
import { companyDetails } from '@/content/company';
import { ProductPageClient } from '@/components/shop/ProductPageClient';
import { getProductVariants } from '@/content/product-variants';
import { JsonLd } from '@/components/ui/JsonLd';
interface Props {
params: Promise<{ slug: string }>;
@@ -51,17 +52,13 @@ export default async function ProductPage({ params }: Props) {
"url": `${baseUrl}/sklep/${product.slug}`,
"priceCurrency": "PLN",
"price": product.price,
"priceValidUntil": "2027-12-31",
"availability": "https://schema.org/InStock",
"seller": { "@type": "Organization", "name": "ThermCool" },
},
} : null;
return (
<>
{productJsonLd && (
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(productJsonLd) }} />
)}
{productJsonLd && <JsonLd data={productJsonLd} />}
<ProductPageClient
product={product}
seriesProducts={seriesProducts}
+3 -3
View File
@@ -8,7 +8,7 @@ import { ArrowLeft, Phone, Zap, Wind, Volume2, Thermometer } from 'lucide-react'
import MultisplitContactForm from '@/components/shop/MultisplitContactForm';
interface Props {
params: { slug: string };
params: Promise<{ slug: string }>;
}
export async function generateStaticParams() {
@@ -18,7 +18,7 @@ export async function generateStaticParams() {
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = params;
const { slug } = await params;
const product = getProductBySlug(slug);
if (!product) return { title: 'Produkt nie znaleziony' };
return {
@@ -28,7 +28,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
}
export default async function MultisplitInquiryPage({ params }: Props) {
const { slug } = params;
const { slug } = await params;
const product = getProductBySlug(slug);
if (!product || product.category !== 'multisplit') notFound();