feat: SEO cleanup, A11y, Next.js 16 dynamic routes, hide unverified content
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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">
|
||||
|
||||
@@ -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
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user