import { Metadata } from 'next';
import Footer from '@/components/ui/Footer';
import { Toaster } from '@/components/ui/Toasts/toaster';
import { PropsWithChildren, Suspense } from 'react';
import { getURL } from '@/utils/helpers';
import 'styles/main.css';
import Script from 'next/script';
import { Inter, Roboto_Condensed } from 'next/font/google';
import NavbarClient from '@/components/ui/NavbarClient';
import GoogleAnalytics from './components/GoogleAnalytics';
// Font optimization - use display:swap to prevent font blocking
const inter = Inter({
subsets: ['latin', 'cyrillic'],
display: 'swap',
variable: '--font-inter',
preload: true, // Ensure font preloading
});
// Properly load Roboto Condensed with next/font/google instead of CSS import
const robotoCondensed = Roboto_Condensed({
subsets: ['latin', 'cyrillic'],
display: 'swap',
variable: '--font-roboto-condensed',
preload: true,
weight: ['400', '500', '700']
});
const title = 'Вкъщи Бар';
const description =
'Организираме събития на живо, настолни игри и куизове в приятелска, социална обстановка с готини коктейли. Настроението е това, което ни отличава.';
// Google Analytics ID
const GA_MEASUREMENT_ID = 'G-1E9WF6B13X';
export const metadata: Metadata = {
metadataBase: new URL(getURL()),
title: {
default: title,
template: `%s | ${title}`
},
description: description,
keywords: ['бар', 'коктейли', 'събития', 'настолни игри', 'куизове', 'София', 'парти', 'резервации'],
authors: [{ name: 'Вкъщи Бар' }],
creator: 'Вкъщи Бар',
publisher: 'Вкъщи Бар',
formatDetection: {
email: false,
address: false,
telephone: false
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-image-preview': 'large',
'max-snippet': -1
}
},
openGraph: {
type: 'website',
locale: 'bg_BG',
url: getURL(),
siteName: title,
images: [
{
url: '/opengraph-image.png',
width: 1686,
height: 882,
alt: 'Вкъщи Бар'
}
],
title: title,
description: description
},
twitter: {
card: 'summary_large_image',
title: title,
description: description,
images: ['/opengraph-image.png']
},
alternates: {
canonical: getURL()
}
};
// JSON-LD structured data for LocalBusiness
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'BarOrPub',
name: 'Вкъщи Бар',
image: `${getURL()}/opengraph-image.png`,
'@id': getURL(),
url: getURL(),
telephone: '+359 87 961 1154',
address: {
'@type': 'PostalAddress',
streetAddress: 'ул. Жеравна 6',
addressLocality: 'София',
postalCode: '1164',
addressCountry: 'BG'
},
geo: {
'@type': 'GeoCoordinates',
latitude: 42.674431,
longitude: 23.342463
},
openingHoursSpecification: [
{
'@type': 'OpeningHoursSpecification',
dayOfWeek: ['Monday', 'Tuesday', 'Wednesday', 'Thursday'],
opens: '14:00',
closes: '00:00'
},
{
'@type': 'OpeningHoursSpecification',
dayOfWeek: ['Friday', 'Saturday'],
opens: '14:00',
closes: '02:00'
},
{
'@type': 'OpeningHoursSpecification',
dayOfWeek: 'Sunday',
opens: '14:00',
closes: '00:00'
}
],
menu: `${getURL()}/menu`,
servesCuisine: 'Bar, Cocktails, Snacks',
priceRange: '$$',
paymentAccepted: 'Cash, Credit Card',
amenityFeature: [
{ name: 'Board Games' },
{ name: 'Quiz Nights' },
{ name: 'Cocktails' },
{ name: 'Coworking' }
]
};
export default function RootLayout({ children }: PropsWithChildren) {
return (
<html lang="bg" className={`${inter.variable} ${robotoCondensed.variable}`}>
<head>
<link rel="canonical" href={getURL()} />
{/* Performance optimization meta tags */}
<meta httpEquiv="x-dns-prefetch-control" content="on" />
<link rel="preconnect" href={getURL()} crossOrigin="anonymous" />
{/* Preload critical LCP images */}
<link
rel="preload"
href="/logo.webp"
as="image"
type="image/webp"
fetchPriority="high"
/>
{/* DNS prefetch for commonly used third-party domains */}
<link rel="dns-prefetch" href="https://www.googletagmanager.com" />
<link rel="dns-prefetch" href="https://fonts.googleapis.com" />
<link rel="dns-prefetch" href="https://fonts.gstatic.com" />
{/* JSON-LD structured data */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
</head>
<body className="min-h-screen flex flex-col">
{/* Skip link for accessibility */}
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:bg-white focus:text-orange-600 focus:px-6 focus:py-3 focus:rounded-lg focus:shadow-lg focus:ring-2 focus:ring-orange-500 focus:font-bold focus:text-lg focus:border-2 focus:border-orange-500"
aria-label="Пропусни до основното съдържание"
>
Пропусни до основното съдържание
</a>
{/* Google Analytics implementation */}
<GoogleAnalytics />
<NavbarClient />
<main id="main-content" className="flex-grow">
{children}
</main>
<Footer />
<Suspense fallback={null}>
<Toaster />
</Suspense>
{/* Fix BroadcastChannel caching issue by ensuring it's only created once needed */}
<Script
id="fix-bfcache"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: `
(function() {
// Save original BroadcastChannel constructor
const OriginalBroadcastChannel = window.BroadcastChannel;
// Replace with a version that only creates instances when actually needed
window.BroadcastChannel = function(channel) {
let instance = null;
let listeners = [];
// Return proxy object that defers instance creation until it's actually used
return new Proxy({}, {
get: function(target, prop) {
if (prop === 'postMessage') {
// Only create instance when actually posting messages
if (!instance) instance = new OriginalBroadcastChannel(channel);
return instance.postMessage.bind(instance);
}
if (prop === 'addEventListener') {
return function(type, listener) {
if (!instance) instance = new OriginalBroadcastChannel(channel);
listeners.push({ type, listener });
instance.addEventListener(type, listener);
};
}
if (prop === 'removeEventListener') {
return function(type, listener) {
if (instance) {
instance.removeEventListener(type, listener);
listeners = listeners.filter(l => l.type !== type || l.listener !== listener);
// If no more listeners, close channel to help with bfcache
if (listeners.length === 0) {
instance.close();
instance = null;
}
}
};
}
if (prop === 'close') {
return function() {
if (instance) {
instance.close();
instance = null;
listeners = [];
}
};
}
// For any other property, create instance if needed
if (!instance) instance = new OriginalBroadcastChannel(channel);
return instance[prop];
}
});
};
})();
`
}}
/>
{/* Preconnect script - executed after page is interactive */}
<Script
id="preconnect-resources"
strategy="lazyOnload"
dangerouslySetInnerHTML={{
__html: `
// Only load connections we actually need
const activeConnections = new Set();
// Connect to a domain only when needed
function connectToDomain(url) {
if (activeConnections.has(url)) return;
const link = document.createElement('link');
link.rel = 'preconnect';
link.href = url;
link.crossOrigin = 'anonymous';
document.head.appendChild(link);
activeConnections.add(url);
}
// Only connect to domains as they're needed
if (document.querySelector('script[src*="google"]')) {
connectToDomain('https://www.google-analytics.com');
}
// Connect to other domains only when they're about to be used
document.addEventListener('mouseover', e => {
const link = e.target.closest('a');
if (link && link.href && link.href.includes('maps.google')) {
connectToDomain('https://maps.googleapis.com');
}
}, { passive: true });
`
}}
/>
</body>
</html>
);
}