The browser never talks to Strapi: pages call Nitro endpoints under server/api/, which are the only place Strapi queries are built. That keeps the Strapi URL internal to the Docker network and avoids CORS entirely. Article bodies are rendered from Markdown to HTML in the endpoint, so the content is server-rendered and crawlable, marked stays out of the client bundle, and the meta description is derived from the opening of the body. Pages carry canonical URLs, Open Graph tags and BlogPosting structured data, and handle empty, missing and failed states.
95 lines
1.8 KiB
Vue
95 lines
1.8 KiB
Vue
<script setup lang="ts">
|
|
import type { Category } from '#shared/types/blog'
|
|
|
|
const { data: categories } = await useFetch<Category[]>('/api/categories', {
|
|
key: 'nav-categories',
|
|
default: () => [],
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="page">
|
|
<a class="skip-link" href="#main">Salta al contenuto</a>
|
|
|
|
<header class="site-header">
|
|
<div class="container header-inner">
|
|
<NuxtLink to="/" class="brand">Blog</NuxtLink>
|
|
<nav aria-label="Navigazione principale">
|
|
<ul>
|
|
<li><NuxtLink to="/blog">Articoli</NuxtLink></li>
|
|
<li v-for="category in categories" :key="category.slug">
|
|
<NuxtLink :to="`/category/${category.slug}`">{{ category.name }}</NuxtLink>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
|
|
<main id="main" class="container site-main">
|
|
<slot />
|
|
</main>
|
|
|
|
<footer class="site-footer">
|
|
<div class="container">
|
|
<p class="muted">© {{ new Date().getFullYear() }} Blog</p>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.page {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.site-header {
|
|
border-bottom: 1px solid var(--color-border);
|
|
}
|
|
|
|
.header-inner {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 1rem 2rem;
|
|
padding-block: 1rem;
|
|
}
|
|
|
|
.brand {
|
|
font-size: 1.25rem;
|
|
font-weight: 700;
|
|
color: inherit;
|
|
text-decoration: none;
|
|
}
|
|
|
|
nav ul {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 1rem;
|
|
padding: 0;
|
|
margin: 0;
|
|
list-style: none;
|
|
}
|
|
|
|
nav a {
|
|
color: inherit;
|
|
text-decoration: none;
|
|
}
|
|
|
|
nav a:hover,
|
|
nav a.router-link-active {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.site-main {
|
|
flex: 1;
|
|
padding-block: 2.5rem 4rem;
|
|
}
|
|
|
|
.site-footer {
|
|
padding-block: 1.5rem;
|
|
border-top: 1px solid var(--color-border);
|
|
}
|
|
</style>
|