feat: enhance metadata, add robots and sitemap routes, and implement custom 404 and OpenGraph images
This commit is contained in:
@ -53,9 +53,19 @@ const nextConfig: NextConfig = {
|
||||
key: "Content-Security-Policy",
|
||||
value: cspHeader.replace(/\s{2,}/g, " ").trim(),
|
||||
},
|
||||
{
|
||||
key: "Strict-Transport-Security",
|
||||
value: "max-age=63072000; includeSubDomains; preload",
|
||||
},
|
||||
{ key: "X-Content-Type-Options", value: "nosniff" },
|
||||
{ key: "X-Frame-Options", value: "DENY" },
|
||||
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
||||
{ key: "X-DNS-Prefetch-Control", value: "on" },
|
||||
{
|
||||
key: "Permissions-Policy",
|
||||
value:
|
||||
"camera=(), microphone=(), geolocation=(self), interest-cohort=()",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
BIN
public/aeris-hero.png
Normal file
BIN
public/aeris-hero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 396 KiB |
@ -14,25 +14,43 @@ const GA_ID = process.env.NEXT_PUBLIC_GA_ID;
|
||||
|
||||
const title = "Aeris — Real-Time 3D Flight Tracking";
|
||||
const description =
|
||||
"Track live flights in 3D over the world's busiest airspaces. Altitude-aware, beautifully rendered, and completely free.";
|
||||
"Track live flights in stunning 3D over the world's busiest airspaces. See real-time ADS-B data with altitude-aware rendering — low altitudes glow cyan, high altitudes shift to gold. Free and open source.";
|
||||
const siteUrl = "https://aeris.edbn.me";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
metadataBase: new URL(siteUrl),
|
||||
title: {
|
||||
default: title,
|
||||
template: "%s | Aeris",
|
||||
},
|
||||
description,
|
||||
applicationName: "Aeris",
|
||||
keywords: [
|
||||
"flight tracker",
|
||||
"live flights",
|
||||
"live flight tracker",
|
||||
"3D flight tracking",
|
||||
"real-time aviation",
|
||||
"real-time flight tracker",
|
||||
"flight radar",
|
||||
"aircraft tracking",
|
||||
"aeris",
|
||||
"opensky",
|
||||
"aircraft tracker",
|
||||
"plane tracker",
|
||||
"ADS-B tracker",
|
||||
"live aircraft map",
|
||||
"flight tracking map",
|
||||
"airplane tracker live",
|
||||
"aviation tracker",
|
||||
"track flights live",
|
||||
"free flight tracker",
|
||||
"aeris flight tracker",
|
||||
"opensky network",
|
||||
"airplanes live",
|
||||
"adsb tracker",
|
||||
"live air traffic",
|
||||
"flight path tracker",
|
||||
],
|
||||
authors: [{ name: "kewonit", url: "https://github.com/kewonit" }],
|
||||
creator: "kewonit",
|
||||
publisher: "kewonit",
|
||||
category: "travel",
|
||||
openGraph: {
|
||||
type: "website",
|
||||
locale: "en_US",
|
||||
@ -49,9 +67,25 @@ export const metadata: Metadata = {
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: { index: true, follow: true },
|
||||
nocache: false,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
"max-video-preview": -1,
|
||||
"max-image-preview": "large",
|
||||
"max-snippet": -1,
|
||||
},
|
||||
},
|
||||
alternates: { canonical: siteUrl },
|
||||
icons: {
|
||||
icon: "/favicon.ico",
|
||||
},
|
||||
other: {
|
||||
"mobile-web-app-capable": "yes",
|
||||
"apple-mobile-web-app-capable": "yes",
|
||||
"apple-mobile-web-app-status-bar-style": "black-translucent",
|
||||
"apple-mobile-web-app-title": "Aeris",
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
||||
22
src/app/manifest.ts
Normal file
22
src/app/manifest.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
name: "Aeris — Real-Time 3D Flight Tracking",
|
||||
short_name: "Aeris",
|
||||
description:
|
||||
"Track live flights in 3D over the world's busiest airspaces. Altitude-aware, beautifully rendered, and completely free.",
|
||||
start_url: "/",
|
||||
display: "standalone",
|
||||
background_color: "#000000",
|
||||
theme_color: "#000000",
|
||||
icons: [
|
||||
{
|
||||
src: "/favicon.ico",
|
||||
sizes: "any",
|
||||
type: "image/x-icon",
|
||||
},
|
||||
],
|
||||
categories: ["travel", "navigation", "utilities"],
|
||||
};
|
||||
}
|
||||
73
src/app/not-found.tsx
Normal file
73
src/app/not-found.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Page Not Found",
|
||||
description:
|
||||
"The page you are looking for does not exist. Return to Aeris to track live flights in 3D.",
|
||||
};
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
minHeight: "100dvh",
|
||||
background: "hsl(0 0% 0%)",
|
||||
color: "hsl(0 0% 100%)",
|
||||
fontFamily: "Inter, system-ui, sans-serif",
|
||||
textAlign: "center",
|
||||
padding: "2rem",
|
||||
}}
|
||||
>
|
||||
<h1
|
||||
style={{
|
||||
fontSize: "6rem",
|
||||
fontWeight: 700,
|
||||
margin: 0,
|
||||
letterSpacing: "-2px",
|
||||
opacity: 0.15,
|
||||
}}
|
||||
>
|
||||
404
|
||||
</h1>
|
||||
<h2
|
||||
style={{
|
||||
fontSize: "1.5rem",
|
||||
fontWeight: 600,
|
||||
margin: "0.5rem 0",
|
||||
}}
|
||||
>
|
||||
Page not found
|
||||
</h2>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "1rem",
|
||||
color: "hsl(0 0% 60%)",
|
||||
maxWidth: "28rem",
|
||||
marginTop: "0.5rem",
|
||||
}}
|
||||
>
|
||||
The page you're looking for doesn't exist or has been moved.
|
||||
</p>
|
||||
<Link
|
||||
href="/"
|
||||
style={{
|
||||
marginTop: "2rem",
|
||||
padding: "0.75rem 2rem",
|
||||
borderRadius: "0.5rem",
|
||||
background: "hsl(0 0% 100%)",
|
||||
color: "hsl(0 0% 0%)",
|
||||
fontSize: "0.875rem",
|
||||
fontWeight: 600,
|
||||
textDecoration: "none",
|
||||
}}
|
||||
>
|
||||
Back to Aeris
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
148
src/app/opengraph-image.tsx
Normal file
148
src/app/opengraph-image.tsx
Normal file
@ -0,0 +1,148 @@
|
||||
import { ImageResponse } from "next/og";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
export const alt = "Aeris — Real-Time 3D Flight Tracking";
|
||||
export const size = { width: 1200, height: 630 };
|
||||
export const contentType = "image/png";
|
||||
|
||||
export default async function Image() {
|
||||
const imageData = await readFile(
|
||||
join(process.cwd(), "public", "aeris-hero.png"),
|
||||
);
|
||||
const base64 = imageData.toString("base64");
|
||||
const heroSrc = `data:image/png;base64,${base64}`;
|
||||
|
||||
return new ImageResponse(
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
fontFamily: "Inter, system-ui, sans-serif",
|
||||
}}
|
||||
>
|
||||
{/* Hero background image */}
|
||||
<img
|
||||
src={heroSrc}
|
||||
alt=""
|
||||
width={1200}
|
||||
height={630}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Full dark vignette overlay */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
background:
|
||||
"linear-gradient(to top right, rgba(0,0,0,0.95) 0%, rgba(0,0,0,0.7) 25%, rgba(0,0,0,0.15) 50%, rgba(0,0,0,0.05) 100%)",
|
||||
display: "flex",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Content overlay pinned to bottom */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
padding: "0 60px 44px 60px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{/* Title */}
|
||||
<div
|
||||
style={{
|
||||
fontSize: "56px",
|
||||
fontWeight: 700,
|
||||
color: "#ffffff",
|
||||
letterSpacing: "-1.5px",
|
||||
lineHeight: 1,
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
Aeris
|
||||
</div>
|
||||
|
||||
{/* Tagline */}
|
||||
<div
|
||||
style={{
|
||||
fontSize: "24px",
|
||||
fontWeight: 400,
|
||||
color: "rgba(255,255,255,0.85)",
|
||||
marginTop: "10px",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
Real-Time 3D Flight Tracking
|
||||
</div>
|
||||
|
||||
{/* Divider + pills row */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "12px",
|
||||
marginTop: "20px",
|
||||
}}
|
||||
>
|
||||
{["Altitude-Aware", "Live ADS-B Data", "Free & Open Source"].map(
|
||||
(label) => (
|
||||
<div
|
||||
key={label}
|
||||
style={{
|
||||
padding: "6px 18px",
|
||||
borderRadius: "100px",
|
||||
background: "rgba(0,0,0,0.75)",
|
||||
border: "1px solid rgba(255,255,255,0.18)",
|
||||
color: "rgba(255,255,255,0.85)",
|
||||
fontSize: "14px",
|
||||
fontWeight: 500,
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* URL badge top-right */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "48px",
|
||||
right: "56px",
|
||||
fontSize: "15px",
|
||||
fontWeight: 600,
|
||||
color: "rgba(255,255,255,0.9)",
|
||||
display: "flex",
|
||||
padding: "8px 18px",
|
||||
borderRadius: "100px",
|
||||
background: "rgba(0,0,0,0.65)",
|
||||
border: "1px solid rgba(255,255,255,0.2)",
|
||||
}}
|
||||
>
|
||||
aeris.edbn.me
|
||||
</div>
|
||||
</div>,
|
||||
{ ...size },
|
||||
);
|
||||
}
|
||||
@ -1,17 +1,83 @@
|
||||
import { FlightTracker } from "@/components/flight-tracker";
|
||||
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebApplication",
|
||||
name: "Aeris",
|
||||
url: "https://aeris.edbn.me",
|
||||
description:
|
||||
"Track live flights in 3D over the world's busiest airspaces. Altitude-aware, beautifully rendered, and completely free.",
|
||||
applicationCategory: "TravelApplication",
|
||||
operatingSystem: "Any",
|
||||
offers: { "@type": "Offer", price: "0", priceCurrency: "USD" },
|
||||
author: { "@type": "Person", name: "kewonit" },
|
||||
};
|
||||
const siteUrl = "https://aeris.edbn.me";
|
||||
|
||||
const jsonLd = [
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebApplication",
|
||||
"@id": `${siteUrl}/#app`,
|
||||
name: "Aeris",
|
||||
url: siteUrl,
|
||||
description:
|
||||
"Track live flights in stunning 3D over the world's busiest airspaces. See real-time ADS-B data with altitude-aware rendering — low altitudes glow cyan, high altitudes shift to gold. Free and open source.",
|
||||
applicationCategory: "TravelApplication",
|
||||
operatingSystem: "Any",
|
||||
browserRequirements: "Requires WebGL support",
|
||||
offers: {
|
||||
"@type": "Offer",
|
||||
price: "0",
|
||||
priceCurrency: "USD",
|
||||
availability: "https://schema.org/OnlineOnly",
|
||||
},
|
||||
author: {
|
||||
"@type": "Person",
|
||||
name: "kewonit",
|
||||
url: "https://github.com/kewonit",
|
||||
},
|
||||
featureList: [
|
||||
"Real-time 3D flight tracking",
|
||||
"Altitude-aware color rendering",
|
||||
"Live ADS-B data from multiple sources",
|
||||
"3D aircraft models",
|
||||
"City-based airspace views",
|
||||
"Live ATC audio streaming",
|
||||
"Flight trail visualization",
|
||||
"Aircraft photo lookup",
|
||||
"Dark mode interface",
|
||||
],
|
||||
screenshot:
|
||||
"https://github.com/user-attachments/assets/9d1f50ed-be4e-4ef5-95ac-257e9129f8c8",
|
||||
softwareVersion: "0.1.0",
|
||||
isAccessibleForFree: true,
|
||||
inLanguage: "en",
|
||||
},
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"@id": `${siteUrl}/#website`,
|
||||
name: "Aeris",
|
||||
url: siteUrl,
|
||||
description:
|
||||
"Real-time 3D flight tracking — altitude-aware, visually stunning, and completely free.",
|
||||
inLanguage: "en",
|
||||
publisher: {
|
||||
"@type": "Person",
|
||||
name: "kewonit",
|
||||
url: "https://github.com/kewonit",
|
||||
},
|
||||
potentialAction: {
|
||||
"@type": "SearchAction",
|
||||
target: {
|
||||
"@type": "EntryPoint",
|
||||
urlTemplate: `${siteUrl}/?q={search_term_string}`,
|
||||
},
|
||||
"query-input": "required name=search_term_string",
|
||||
},
|
||||
},
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
itemListElement: [
|
||||
{
|
||||
"@type": "ListItem",
|
||||
position: 1,
|
||||
name: "Aeris — Real-Time 3D Flight Tracking",
|
||||
item: siteUrl,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
|
||||
14
src/app/robots.ts
Normal file
14
src/app/robots.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: [
|
||||
{
|
||||
userAgent: "*",
|
||||
allow: "/",
|
||||
disallow: ["/api/", "/private/"],
|
||||
},
|
||||
],
|
||||
sitemap: "https://aeris.edbn.me/sitemap.xml",
|
||||
};
|
||||
}
|
||||
12
src/app/sitemap.ts
Normal file
12
src/app/sitemap.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return [
|
||||
{
|
||||
url: "https://aeris.edbn.me",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "daily",
|
||||
priority: 1,
|
||||
},
|
||||
];
|
||||
}
|
||||
148
src/app/twitter-image.tsx
Normal file
148
src/app/twitter-image.tsx
Normal file
@ -0,0 +1,148 @@
|
||||
import { ImageResponse } from "next/og";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
export const alt = "Aeris — Real-Time 3D Flight Tracking";
|
||||
export const size = { width: 1200, height: 630 };
|
||||
export const contentType = "image/png";
|
||||
|
||||
export default async function Image() {
|
||||
const imageData = await readFile(
|
||||
join(process.cwd(), "public", "aeris-hero.png"),
|
||||
);
|
||||
const base64 = imageData.toString("base64");
|
||||
const heroSrc = `data:image/png;base64,${base64}`;
|
||||
|
||||
return new ImageResponse(
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
fontFamily: "Inter, system-ui, sans-serif",
|
||||
}}
|
||||
>
|
||||
{/* Hero background image */}
|
||||
<img
|
||||
src={heroSrc}
|
||||
alt=""
|
||||
width={1200}
|
||||
height={630}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Full dark vignette overlay */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
background:
|
||||
"linear-gradient(to top right, rgba(0,0,0,0.95) 0%, rgba(0,0,0,0.7) 25%, rgba(0,0,0,0.15) 50%, rgba(0,0,0,0.05) 100%)",
|
||||
display: "flex",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Content overlay pinned to bottom */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
padding: "0 60px 44px 60px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{/* Title */}
|
||||
<div
|
||||
style={{
|
||||
fontSize: "56px",
|
||||
fontWeight: 700,
|
||||
color: "#ffffff",
|
||||
letterSpacing: "-1.5px",
|
||||
lineHeight: 1,
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
Aeris
|
||||
</div>
|
||||
|
||||
{/* Tagline */}
|
||||
<div
|
||||
style={{
|
||||
fontSize: "24px",
|
||||
fontWeight: 400,
|
||||
color: "rgba(255,255,255,0.85)",
|
||||
marginTop: "10px",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
Real-Time 3D Flight Tracking
|
||||
</div>
|
||||
|
||||
{/* Divider + pills row */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "12px",
|
||||
marginTop: "20px",
|
||||
}}
|
||||
>
|
||||
{["Altitude-Aware", "Live ADS-B Data", "Free & Open Source"].map(
|
||||
(label) => (
|
||||
<div
|
||||
key={label}
|
||||
style={{
|
||||
padding: "6px 18px",
|
||||
borderRadius: "100px",
|
||||
background: "rgba(0,0,0,0.75)",
|
||||
border: "1px solid rgba(255,255,255,0.18)",
|
||||
color: "rgba(255,255,255,0.85)",
|
||||
fontSize: "14px",
|
||||
fontWeight: 500,
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* URL badge top-right */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "48px",
|
||||
right: "56px",
|
||||
fontSize: "15px",
|
||||
fontWeight: 600,
|
||||
color: "rgba(255,255,255,0.9)",
|
||||
display: "flex",
|
||||
padding: "8px 18px",
|
||||
borderRadius: "100px",
|
||||
background: "rgba(0,0,0,0.65)",
|
||||
border: "1px solid rgba(255,255,255,0.2)",
|
||||
}}
|
||||
>
|
||||
aeris.edbn.me
|
||||
</div>
|
||||
</div>,
|
||||
{ ...size },
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user