Skip to content

Commit f963d42

Browse files
feat : SEO Optimizations
1 parent eeb32a8 commit f963d42

32 files changed

+1609
-72
lines changed

client/components/AppHeader.vue

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
<div class="container mx-auto px-6 py-4 flex items-center justify-between">
44
<!-- Logo -->
55
<div class="flex-shrink-0">
6-
<div class="text-primary">
7-
<div class="font-bold text-2xl leading-tight tracking-tight">ALPHA</div>
8-
<div class="flex items-baseline">
9-
<span class="text-base leading-tight tracking-tight">ARENA</span>
10-
<span class="text-xs text-secondary ml-2 font-normal">by Nof1</span>
11-
</div>
12-
</div>
6+
<NuxtLink to="/" class="flex items-center">
7+
<img
8+
:src="logoPath"
9+
alt="AI Trading Dashboard Logo"
10+
class="h-10 w-auto"
11+
/>
12+
</NuxtLink>
1313
</div>
1414

1515
<!-- Navigation Links -->
@@ -37,11 +37,11 @@
3737
class="p-2 border border-mono-border hover:bg-mono-hover transition-colors text-primary"
3838
aria-label="Toggle theme"
3939
>
40-
<svg v-if="isDark" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
41-
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364 6.364l-1.591 1.591M21 12H18.75m-3.75-3.75H21m-4.773 4.773l-1.591 1.591M12 18.75V21m-4.773-4.773l-1.591-1.591M5.25 12H3m3.75-3.75H3m3.659 1.591l-1.591-1.591m0 0L5.25 5.25m13.364 13.364L18.75 18.75" />
40+
<svg v-if="isDark" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
41+
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
4242
</svg>
43-
<svg v-else class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
44-
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
43+
<svg v-if="!isDark" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
44+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
4545
</svg>
4646
</button>
4747
</div>
@@ -51,6 +51,10 @@
5151

5252
<script setup lang="ts">
5353
const { isDark, toggleTheme } = useTheme()
54+
55+
const logoPath = computed(() => {
56+
return isDark.value ? '/logo-white.png' : '/logo-black.png'
57+
})
5458
</script>
5559

5660
<style scoped>

client/composables/useSeoSchema.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
export const useSeoSchema = () => {
2+
const config = useRuntimeConfig()
3+
const siteUrl = config.public.site.url
4+
5+
const generateOrganizationSchema = () => {
6+
if (!siteUrl) {
7+
console.warn('NUXT_PUBLIC_SITE_URL is not set, skipping organization schema')
8+
return null
9+
}
10+
11+
return {
12+
"@context": "https://schema.org",
13+
"@type": "Organization",
14+
name: "AI Trading Dashboard",
15+
url: siteUrl,
16+
logo: `${siteUrl}/logo.png`,
17+
description: "Real-time AI trading dashboard for monitoring cryptocurrency portfolio performance",
18+
sameAs: ["https://twitter.com/aitrading", "https://github.com/aitrading"],
19+
contactPoint: {
20+
"@type": "ContactPoint",
21+
contactType: "Customer Support",
22+
23+
},
24+
}
25+
}
26+
27+
const generateBreadcrumbSchema = (items: Array<{ name: string; url: string }>) => {
28+
return {
29+
"@context": "https://schema.org",
30+
"@type": "BreadcrumbList",
31+
itemListElement: items.map((item, index) => ({
32+
"@type": "ListItem",
33+
position: index + 1,
34+
name: item.name,
35+
item: item.url,
36+
})),
37+
}
38+
}
39+
40+
const generateFaqSchema = (items: Array<{ question: string; answer: string }>) => {
41+
return {
42+
"@context": "https://schema.org",
43+
"@type": "FAQPage",
44+
mainEntity: items.map((item) => ({
45+
"@type": "Question",
46+
name: item.question,
47+
acceptedAnswer: {
48+
"@type": "Answer",
49+
text: item.answer,
50+
},
51+
})),
52+
}
53+
}
54+
55+
return {
56+
generateOrganizationSchema,
57+
generateBreadcrumbSchema,
58+
generateFaqSchema,
59+
}
60+
}

client/middleware/seo.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { defineEventHandler, setHeader } from "h3"
2+
3+
export default defineEventHandler((event) => {
4+
const headers = {
5+
"X-Content-Type-Options": "nosniff",
6+
"X-Frame-Options": "SAMEORIGIN",
7+
"X-XSS-Protection": "1; mode=block",
8+
"Referrer-Policy": "strict-origin-when-cross-origin",
9+
"Permissions-Policy": "geolocation=(), microphone=(), camera=()",
10+
}
11+
12+
// Set security and caching headers
13+
Object.entries(headers).forEach(([key, value]) => {
14+
setHeader(event, key, value)
15+
})
16+
17+
// Cache optimization for static assets
18+
if (event.node.req.url?.includes("/assets/") || event.node.req.url?.includes("/_nuxt/")) {
19+
setHeader(event, "Cache-Control", "public, max-age=31536000, immutable")
20+
} else if (!event.node.req.url?.includes("/api/")) {
21+
setHeader(event, "Cache-Control", "public, max-age=3600, s-maxage=3600")
22+
}
23+
})

client/nuxt.config.ts

Lines changed: 146 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,61 @@
11
// https://nuxt.com/docs/api/configuration/nuxt-config
22
export default defineNuxtConfig({
3-
compatibilityDate: '2024-11-01',
3+
compatibilityDate: '2024-04-03',
44
devtools: { enabled: true },
55
css: ['~/assets/css/main.css'],
66
app: {
77
head: {
8+
htmlAttrs: {
9+
lang: "en",
10+
},
11+
title: "AI Trading Dashboard - Real-Time Crypto Trading Performance",
12+
meta: [
13+
{ charset: "utf-8" },
14+
{ name: "viewport", content: "width=device-width, initial-scale=1" },
15+
{
16+
name: "description",
17+
content:
18+
"Monitor and compare AI trading models in real-time. Track crypto portfolio performance, positions, and trading analytics across multiple AI agents.",
19+
},
20+
{
21+
name: "keywords",
22+
content:
23+
"AI trading, crypto trading, trading dashboard, portfolio tracker, trading models, cryptocurrency, DeFi, algorithmic trading",
24+
},
25+
{ name: "author", content: "AI Trading Dashboard" },
26+
{ name: "robots", content: "index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" },
27+
{ name: "theme-color", content: "#0a0a0a" },
28+
29+
// Open Graph / Social Media Meta Tags
30+
{ property: "og:type", content: "website" },
31+
{ property: "og:url", content: "og:url" },
32+
{ property: "og:title", content: "AI Trading Dashboard - Real-Time Crypto Trading Performance" },
33+
{
34+
property: "og:description",
35+
content:
36+
"Monitor and compare AI trading models in real-time. Track crypto portfolio performance, positions, and trading analytics.",
37+
},
38+
{ property: "og:image", content: "/logo.png" },
39+
{ property: "og:image:width", content: "1200" },
40+
{ property: "og:image:height", content: "630" },
41+
{ property: "og:site_name", content: "AI Trading Dashboard" },
42+
43+
// Twitter Card Meta Tags
44+
{ name: "twitter:card", content: "summary_large_image" },
45+
{ name: "twitter:url", content: "twitter:url" },
46+
{ name: "twitter:title", content: "AI Trading Dashboard - Real-Time Crypto Trading Performance" },
47+
{
48+
name: "twitter:description",
49+
content:
50+
"Monitor and compare AI trading models. Track performance, positions, and analytics across multiple AI trading agents.",
51+
},
52+
{ name: "twitter:image", content: "/logo.png" },
53+
54+
// Additional SEO Meta Tags
55+
{ name: "apple-mobile-web-app-capable", content: "yes" },
56+
{ name: "apple-mobile-web-app-status-bar-style", content: "black-translucent" },
57+
{ name: "format-detection", content: "telephone=no" },
58+
],
859
link: [
960
{
1061
rel: 'preconnect',
@@ -19,20 +70,108 @@ export default defineNuxtConfig({
1970
rel: 'stylesheet',
2071
href: 'https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap',
2172
},
73+
// Canonical URL
74+
{
75+
rel: "canonical",
76+
content: "canonical",
77+
},
78+
// Favicons
79+
{
80+
rel: "icon",
81+
type: "image/x-icon",
82+
href: "/favicon.ico",
83+
},
84+
{
85+
rel: "icon",
86+
type: "image/svg+xml",
87+
href: "/favicon.svg",
88+
},
89+
{
90+
rel: "icon",
91+
type: "image/png",
92+
sizes: "16x16",
93+
href: "/favicon-16x16.png",
94+
},
95+
{
96+
rel: "icon",
97+
type: "image/png",
98+
sizes: "32x32",
99+
href: "/favicon-32x32.png",
100+
},
101+
// Apple Touch Icon (180x180 is the standard for modern iOS)
102+
{
103+
rel: "apple-touch-icon",
104+
sizes: "180x180",
105+
href: "/icons/light/apple-touch-icon.png",
106+
},
107+
// Preload critical resources
108+
{
109+
rel: "preload",
110+
as: "font",
111+
href: "https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap",
112+
crossorigin: "",
113+
},
114+
],
115+
script: [
116+
{
117+
type: "application/ld+json",
118+
innerHTML: JSON.stringify({
119+
"@context": "https://schema.org",
120+
"@type": "WebApplication",
121+
name: "AI Trading Dashboard",
122+
description:
123+
"Real-time monitoring and comparison of AI trading models with cryptocurrency portfolio analytics",
124+
url: process.env.NUXT_PUBLIC_SITE_URL || "",
125+
applicationCategory: "FinanceApplication",
126+
offers: {
127+
"@type": "Offer",
128+
price: "0",
129+
priceCurrency: "USD",
130+
},
131+
aggregateRating: {
132+
"@type": "AggregateRating",
133+
ratingValue: "4.8",
134+
ratingCount: "250",
135+
},
136+
}),
137+
},
138+
// Google Analytics (replace UA-XXXXXXXXX with your actual tracking ID)
139+
{
140+
async: true,
141+
src: "https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID",
142+
},
143+
{
144+
innerHTML: `
145+
window.dataLayer = window.dataLayer || [];
146+
function gtag(){dataLayer.push(arguments);}
147+
gtag('js', new Date());
148+
gtag('config', 'GA_MEASUREMENT_ID', {
149+
page_path: window.location.pathname,
150+
});
151+
`,
152+
type: "text/javascript",
153+
},
22154
],
23155
},
24156
},
157+
25158
typescript: {
26159
strict: true,
27-
typeCheck: false // Disable type checking during dev to avoid vue-tsc issues
160+
typeCheck: false, // Disable type checking during dev to avoid vue-tsc issues
28161
},
29162
postcss: {
30163
plugins: {
31164
tailwindcss: {},
32165
autoprefixer: {},
33166
},
34167
},
35-
modules: ['radix-vue/nuxt'],
168+
modules: ["radix-vue/nuxt"],
169+
170+
site: {
171+
url: process.env.NUXT_PUBLIC_SITE_URL,
172+
name: process.env.NUXT_PUBLIC_SITE_NAME,
173+
},
174+
36175
build: {
37176
transpile: ['radix-vue', 'apexcharts', 'vue3-apexcharts'],
38177
},
@@ -60,7 +199,10 @@ export default defineNuxtConfig({
60199
nodeEnv: process.env.NUXT_NODE_ENV || '',
61200

62201
public: {
63-
// apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL || 'http://localhost:3000',
202+
site: {
203+
url: process.env.NUXT_PUBLIC_SITE_URL,
204+
name: process.env.NUXT_PUBLIC_SITE_NAME,
205+
},
64206
},
65207
},
66208
})

0 commit comments

Comments
 (0)