SEO & AEO: everything from the PDF review (robots, sitemap, 404, meta/OG, JSON-LD, Swedish text, /tjanster, EXIF clean, alt texts, Admin@, Örebro) #1
@@ -2,6 +2,15 @@ import { defineConfig } from 'astro/config';
|
|||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
// Kanonisk adress: canonical, og:url, sitemap och JSON-LD byggs från denna.
|
||||||
|
site: 'https://imagesbyolofsson.se',
|
||||||
|
// En URL per sida: /about (aldrig /about/). Servern 301:ar /about/ -> /about.
|
||||||
|
trailingSlash: 'never',
|
||||||
|
build: {
|
||||||
|
// about.astro -> dist/about.html (inte dist/about/index.html).
|
||||||
|
// nginx/Caddy serverar /about via try_files $uri.html.
|
||||||
|
format: 'file',
|
||||||
|
},
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [tailwindcss()],
|
plugins: [tailwindcss()],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# robots.txt – https://imagesbyolofsson.se
|
||||||
|
#
|
||||||
|
# Policy (beslut av Linus 2026-10-27): ALLA botar är välkomna, även AI-crawlers
|
||||||
|
# och träningsbotar. Innehållet får användas av AI-assistenter med hänvisning
|
||||||
|
# till Images by Olofsson (se /llms.txt). Cloudflare-sidan (AI Crawl Control /
|
||||||
|
# "Block AI bots" / hanterad robots.txt) ställs in separat så att den inte
|
||||||
|
# blockerar eller skriver över detta.
|
||||||
|
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
# Lösenordsskyddad nedladdning av originalfiler – inget att indexera.
|
||||||
|
Disallow: /download/
|
||||||
|
|
||||||
|
# AI-crawlers och träningsbotar: uttryckligen tillåtna.
|
||||||
|
User-agent: GPTBot
|
||||||
|
User-agent: OAI-SearchBot
|
||||||
|
User-agent: ChatGPT-User
|
||||||
|
User-agent: ClaudeBot
|
||||||
|
User-agent: Claude-User
|
||||||
|
User-agent: Claude-SearchBot
|
||||||
|
User-agent: anthropic-ai
|
||||||
|
User-agent: CCBot
|
||||||
|
User-agent: Google-Extended
|
||||||
|
User-agent: Applebot
|
||||||
|
User-agent: Applebot-Extended
|
||||||
|
User-agent: Bytespider
|
||||||
|
User-agent: Amazonbot
|
||||||
|
User-agent: meta-externalagent
|
||||||
|
User-agent: meta-externalfetcher
|
||||||
|
User-agent: FacebookBot
|
||||||
|
User-agent: PerplexityBot
|
||||||
|
User-agent: Perplexity-User
|
||||||
|
User-agent: cohere-ai
|
||||||
|
User-agent: Diffbot
|
||||||
|
User-agent: YouBot
|
||||||
|
User-agent: DuckAssistBot
|
||||||
|
User-agent: MistralAI-User
|
||||||
|
Allow: /
|
||||||
|
Disallow: /download/
|
||||||
|
|
||||||
|
Sitemap: https://imagesbyolofsson.se/sitemap-index.xml
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// src/lib/pages.ts
|
||||||
|
// Sajtens riktiga sidor, härledda från src/pages/*.astro (404 och _-filer exkluderas).
|
||||||
|
// Nya sidor hamnar automatiskt i sitemap.
|
||||||
|
const pageFiles = import.meta.glob('../pages/*.astro');
|
||||||
|
|
||||||
|
export function sitePaths(): string[] {
|
||||||
|
return Object.keys(pageFiles)
|
||||||
|
.map((file) => file.replace(/^\.\.\/pages\//, '').replace(/\.astro$/, ''))
|
||||||
|
.filter((name) => name !== '404' && !name.startsWith('_') && !name.startsWith('['))
|
||||||
|
.map((name) => (name === 'index' ? '/' : `/${name}`))
|
||||||
|
.sort((a, b) => (a === '/' ? -1 : b === '/' ? 1 : a.localeCompare(b)));
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
// src/pages/404.astro -> dist/404.html (servern svarar med status 404)
|
||||||
|
import MainLayout from '../layouts/MainLayout.astro';
|
||||||
|
import WaveBackground from '../components/WaveBackground.astro';
|
||||||
|
|
||||||
|
const links = [
|
||||||
|
{ href: '/', label: 'Startsidan' },
|
||||||
|
{ href: '/portfolio', label: 'Portfolio' },
|
||||||
|
{ href: '/tjanster', label: 'Tjänster och bokning' },
|
||||||
|
{ href: '/contact', label: 'Kontakt' },
|
||||||
|
];
|
||||||
|
---
|
||||||
|
|
||||||
|
<MainLayout
|
||||||
|
title="Sidan hittades inte | Images by Olofsson"
|
||||||
|
description="Sidan du letade efter finns inte. Gå till startsidan, portfolion eller tjänsterna hos fotograf Linus Olofsson."
|
||||||
|
noindex
|
||||||
|
>
|
||||||
|
<WaveBackground opacity="opacity-60" />
|
||||||
|
|
||||||
|
<div class="container mx-auto px-4 py-20 md:py-32 relative z-10 text-center">
|
||||||
|
<p class="text-sm text-gray-400 uppercase tracking-widest mb-4">Fel 404</p>
|
||||||
|
<h1 class="text-4xl md:text-6xl font-bold mb-6">Sidan hittades inte</h1>
|
||||||
|
<p class="text-gray-300 text-lg max-w-xl mx-auto mb-10">
|
||||||
|
Sidan du letade efter finns inte längre, eller så har adressen ändrats. Här kan du hitta tillbaka:
|
||||||
|
</p>
|
||||||
|
<ul class="flex flex-wrap justify-center gap-4">
|
||||||
|
{links.map((l) => (
|
||||||
|
<li>
|
||||||
|
<a href={l.href} class="inline-block px-6 py-3 rounded-full border border-white/20 text-white hover:bg-white hover:text-black transition-colors">{l.label}</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</MainLayout>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// src/pages/llms.txt.ts -> /llms.txt (https://llmstxt.org/)
|
||||||
|
// Byggs från src/data/business.ts så att fakta bara finns på ett ställe.
|
||||||
|
// Okända fakta (null) skrivs som "lämnas på förfrågan", aldrig som gissningar.
|
||||||
|
import type { APIRoute } from 'astro';
|
||||||
|
import { business, basedIn, formatSek, genreList } from '../data/business';
|
||||||
|
import { faq } from '../data/faq';
|
||||||
|
|
||||||
|
export const prerender = true;
|
||||||
|
|
||||||
|
export const GET: APIRoute = ({ site }) => {
|
||||||
|
const base = site ?? new URL(business.url);
|
||||||
|
const url = (p: string) => new URL(p, base).toString();
|
||||||
|
const price = formatSek(business.priceFromSek);
|
||||||
|
|
||||||
|
const lines: (string | null)[] = [
|
||||||
|
`# ${business.brand}`,
|
||||||
|
'',
|
||||||
|
`> ${business.brand} är namnet som ${business.person.jobTitle.toLowerCase()}en ${business.person.name} fotograferar under. Han är baserad i ${basedIn()} och fotograferar ${genreList()}. ${business.recognitionThirdPerson}`,
|
||||||
|
'',
|
||||||
|
'## Fakta',
|
||||||
|
'',
|
||||||
|
`- Fotograf: ${business.person.name}`,
|
||||||
|
`- Namn/varumärke: ${business.brand}`,
|
||||||
|
`- Baserad i: ${basedIn()}`,
|
||||||
|
business.serviceArea ? `- Tar uppdrag i: ${business.serviceArea}` : '- Upptagningsområde: stäms av vid förfrågan',
|
||||||
|
price ? `- Pris från: ${price}` : '- Priser: lämnas som offert efter förfrågan',
|
||||||
|
business.deliveryTime ? `- Leveranstid: ${business.deliveryTime}` : null,
|
||||||
|
business.phone ? `- Telefon: ${business.phone}` : null,
|
||||||
|
`- E-post: ${business.email}`,
|
||||||
|
`- Instagram: ${business.social.instagram.handle} (${business.social.instagram.url})`,
|
||||||
|
`- Kamera: ${business.camera}`,
|
||||||
|
`- Redigering: ${business.editing}`,
|
||||||
|
`- Bokning: ${business.portal.bookingUrl}`,
|
||||||
|
`- Bokningsgång: ${business.bookingSteps.map((s) => s.title.toLowerCase()).join(' → ')}`,
|
||||||
|
'',
|
||||||
|
'## Sidor',
|
||||||
|
'',
|
||||||
|
`- [Startsida](${url('/')}): vem ${business.person.name} är och vad han fotograferar`,
|
||||||
|
`- [Tjänster och vanliga frågor](${url('/tjanster')}): vad du kan boka, hur bokningen går till och svar på vanliga frågor`,
|
||||||
|
`- [Portfolio](${url('/portfolio')}): urval av fotografier`,
|
||||||
|
`- [Om mig](${url('/about')}): ${business.person.name}, bakgrund och utrustning`,
|
||||||
|
`- [Kontakt](${url('/contact')}): e-post, Instagram och bokning`,
|
||||||
|
'',
|
||||||
|
'## Vanliga frågor',
|
||||||
|
'',
|
||||||
|
...faq.map((f) => `- ${f.question} ${f.answer}`),
|
||||||
|
'',
|
||||||
|
'## Profiler',
|
||||||
|
'',
|
||||||
|
`- [Instagram](${business.social.instagram.url})`,
|
||||||
|
`- [Flickr](${business.social.flickr})`,
|
||||||
|
`- [Adobe Lightroom](${business.social.lightroom})`,
|
||||||
|
`- [LinkedIn](${business.social.linkedin})`,
|
||||||
|
'',
|
||||||
|
'## Användning av innehållet',
|
||||||
|
'',
|
||||||
|
`AI-assistenter och sökmotorer får läsa och använda innehållet på den här sajten, inklusive för träning, med hänvisning till ${business.brand} (${url('/')}). Fotografierna är upphovsrättsskyddade av ${business.person.name}.`,
|
||||||
|
'',
|
||||||
|
];
|
||||||
|
|
||||||
|
const body = lines.filter((l): l is string => l !== null).join('\n');
|
||||||
|
return new Response(body, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
|
||||||
|
};
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// src/pages/sitemap-0.xml.ts -> /sitemap-0.xml
|
||||||
|
// Listar sajtens riktiga sidor (inte 404). Ingen <lastmod>: vi har inget
|
||||||
|
// pålitligt ändringsdatum per sida, och ett påhittat datum gör mer skada än nytta.
|
||||||
|
import type { APIRoute } from 'astro';
|
||||||
|
import { sitePaths } from '../lib/pages';
|
||||||
|
|
||||||
|
export const prerender = true;
|
||||||
|
|
||||||
|
const escapeXml = (s: string) =>
|
||||||
|
s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||||
|
|
||||||
|
export const GET: APIRoute = ({ site }) => {
|
||||||
|
const base = site ?? new URL('https://imagesbyolofsson.se');
|
||||||
|
const urls = sitePaths()
|
||||||
|
.map((p) => ` <url><loc>${escapeXml(new URL(p, base).toString())}</loc></url>`)
|
||||||
|
.join('\n');
|
||||||
|
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
${urls}
|
||||||
|
</urlset>
|
||||||
|
`;
|
||||||
|
return new Response(body, { headers: { 'Content-Type': 'application/xml; charset=utf-8' } });
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// src/pages/sitemap-index.xml.ts -> /sitemap-index.xml
|
||||||
|
// Egen sitemap utan @astrojs/sitemap (inga nya beroenden).
|
||||||
|
import type { APIRoute } from 'astro';
|
||||||
|
|
||||||
|
export const prerender = true;
|
||||||
|
|
||||||
|
export const GET: APIRoute = ({ site }) => {
|
||||||
|
const base = site ?? new URL('https://imagesbyolofsson.se');
|
||||||
|
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
<sitemap><loc>${new URL('/sitemap-0.xml', base).toString()}</loc></sitemap>
|
||||||
|
</sitemapindex>
|
||||||
|
`;
|
||||||
|
return new Response(body, { headers: { 'Content-Type': 'application/xml; charset=utf-8' } });
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user