Refactor routing in App component to enhance navigation and improve error handling by integrating dynamic routes and updating the NotFound route.
This commit is contained in:
160
src/layouts/BaseLayout.astro
Normal file
160
src/layouts/BaseLayout.astro
Normal file
@@ -0,0 +1,160 @@
|
||||
---
|
||||
import { SITE } from '../site.config';
|
||||
import { getCurrentLocale } from '../utils/i18n';
|
||||
import '../styles/global.css';
|
||||
|
||||
export interface Props {
|
||||
title?: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
lang?: string;
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
title = SITE.title,
|
||||
description = SITE.description,
|
||||
image = SITE.ogImage,
|
||||
lang = getCurrentLocale(),
|
||||
keywords = ''
|
||||
} = Astro.props;
|
||||
|
||||
// Safe URL creation with fallbacks
|
||||
let canonicalURL;
|
||||
let ogImageURL;
|
||||
let twitterImageURL;
|
||||
|
||||
try {
|
||||
const siteURL = Astro.site || new URL('http://localhost:4321');
|
||||
canonicalURL = new URL(Astro.url.pathname, siteURL);
|
||||
ogImageURL = new URL(image, siteURL);
|
||||
twitterImageURL = new URL(image, siteURL);
|
||||
} catch (error) {
|
||||
// Fallback URLs if there's an issue
|
||||
const fallbackSite = 'https://tiber365.it';
|
||||
canonicalURL = new URL(Astro.url?.pathname || '/', fallbackSite);
|
||||
ogImageURL = new URL(image, fallbackSite);
|
||||
twitterImageURL = new URL(image, fallbackSite);
|
||||
}
|
||||
|
||||
const fullTitle = title === SITE.title ? title : `${title} | ${SITE.title}`;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={lang} class="scroll-smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content={description} />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
{keywords && <meta name="keywords" content={keywords} />}
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>{fullTitle}</title>
|
||||
<meta name="title" content={fullTitle} />
|
||||
<meta name="description" content={description} />
|
||||
<meta name="author" content={SITE.author} />
|
||||
|
||||
<!-- Prevent automatic language redirects -->
|
||||
<meta name="google" content="notranslate" />
|
||||
<meta http-equiv="Content-Language" content={lang} />
|
||||
|
||||
<!-- Theme Color -->
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta name="msapplication-TileColor" content="#3b82f6" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={canonicalURL} />
|
||||
<meta property="og:title" content={fullTitle} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:image" content={ogImageURL} />
|
||||
<meta property="og:site_name" content={SITE.title} />
|
||||
<meta property="og:locale" content={lang === 'en' ? 'en_US' : lang === 'nl' ? 'nl_NL' : 'it_IT'} />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={canonicalURL} />
|
||||
<meta property="twitter:title" content={fullTitle} />
|
||||
<meta property="twitter:description" content={description} />
|
||||
<meta property="twitter:image" content={twitterImageURL} />
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
<!-- Preconnect to external domains -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
|
||||
<!-- Content Security Policy -->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self'; frame-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self';" />
|
||||
|
||||
<!-- Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
"name": "Tiber365",
|
||||
"url": "https://tiber365.it",
|
||||
"logo": "https://tiber365.it/images/logo.png",
|
||||
"description": "Professional IT services for freelancers and small businesses. Microsoft 365 support, networking solutions, web hosting, and custom IT projects.",
|
||||
"contactPoint": {
|
||||
"@type": "ContactPoint",
|
||||
"telephone": "+39-123-456-7890",
|
||||
"contactType": "customer service",
|
||||
"email": "info@tiber365.it"
|
||||
},
|
||||
"sameAs": [
|
||||
"https://blog.tiber365.it",
|
||||
"https://support.tiber365.it"
|
||||
],
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"addressCountry": "IT"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Initialize theme before page load -->
|
||||
<script is:inline>
|
||||
(function() {
|
||||
function getThemePreference() {
|
||||
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
|
||||
return localStorage.getItem('theme');
|
||||
}
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
function reflectPreference(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
const themeColorMeta = document.querySelector('meta[name="theme-color"]');
|
||||
if (themeColorMeta) {
|
||||
themeColorMeta.setAttribute('content', theme === 'dark' ? '#0f172a' : '#ffffff');
|
||||
}
|
||||
}
|
||||
|
||||
const theme = getThemePreference();
|
||||
reflectPreference(theme);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body class="min-h-screen bg-background text-foreground">
|
||||
<slot />
|
||||
|
||||
<!-- Initialize animations -->
|
||||
<script>
|
||||
import { initScrollAnimations } from '../utils/animations';
|
||||
import { initTheme } from '../utils/theme';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initTheme();
|
||||
initScrollAnimations();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user