From 3915f11ef22ec139499115fe57ed247fbc149d02 Mon Sep 17 00:00:00 2001 From: Tobiasz Gleba Date: Fri, 22 Aug 2025 19:17:06 +0200 Subject: [PATCH] feat: improve SEO --- apps/marketing-app/next.config.ts | 58 +++++++++ apps/marketing-app/src/app/(app)/page.tsx | 60 +++++++-- apps/marketing-app/src/app/layout.tsx | 77 ++++++++++- apps/marketing-app/src/app/robots.txt | 27 ++++ .../marketing-app/src/app/structured-data.tsx | 120 ++++++++++++++++++ .../src/app/why-use-our-app/page.tsx | 36 ++++++ 6 files changed, 363 insertions(+), 15 deletions(-) create mode 100644 apps/marketing-app/src/app/robots.txt create mode 100644 apps/marketing-app/src/app/structured-data.tsx diff --git a/apps/marketing-app/next.config.ts b/apps/marketing-app/next.config.ts index 06a43953..b19a5160 100644 --- a/apps/marketing-app/next.config.ts +++ b/apps/marketing-app/next.config.ts @@ -4,6 +4,64 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { reactStrictMode: true, transpilePackages: ["@asyncstatus/ui"], + + // Performance optimizations + compress: true, + poweredByHeader: false, + + // Image optimization + images: { + formats: ["image/avif", "image/webp"], + minimumCacheTTL: 31536000, // 1 year + dangerouslyAllowSVG: true, + contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", + }, + + // Headers for SEO and security + async headers() { + return [ + { + source: "/(.*)", + headers: [ + { + key: "X-Content-Type-Options", + value: "nosniff", + }, + { + key: "X-Frame-Options", + value: "DENY", + }, + { + key: "X-XSS-Protection", + value: "1; mode=block", + }, + { + key: "Referrer-Policy", + value: "strict-origin-when-cross-origin", + }, + ], + }, + // Cache static assets + { + source: "/favicon.ico", + headers: [ + { + key: "Cache-Control", + value: "public, max-age=31536000, immutable", + }, + ], + }, + { + source: "/(.*\\.(?:ico|png|jpg|jpeg|gif|svg|webp|avif))", + headers: [ + { + key: "Cache-Control", + value: "public, max-age=31536000, immutable", + }, + ], + }, + ]; + }, }; export default nextConfig; diff --git a/apps/marketing-app/src/app/(app)/page.tsx b/apps/marketing-app/src/app/(app)/page.tsx index 8ed67021..a80d6fd3 100644 --- a/apps/marketing-app/src/app/(app)/page.tsx +++ b/apps/marketing-app/src/app/(app)/page.tsx @@ -1,6 +1,33 @@ import { AsyncStatusLogo } from "@asyncstatus/ui/components/async-status-logo"; import { Button } from "@asyncstatus/ui/components/button"; import Link from "next/link"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Home", + description: "Replace daily standup meetings with automated status updates. AsyncStatus saves your team 2-3 hours per week by generating updates from Git, Jira, and Slack activity automatically.", + openGraph: { + title: "AsyncStatus - Automated Status Updates for Remote Teams", + description: "Replace daily standup meetings with automated status updates. Save 2-3 hours per developer per week.", + url: "https://asyncstatus.com", + images: [ + { + url: "/opengraph-image.jpg", + width: 1200, + height: 630, + alt: "AsyncStatus - No more daily standups, just automated status updates", + }, + ], + }, + twitter: { + card: "summary_large_image", + title: "AsyncStatus - Automated Status Updates for Remote Teams", + description: "Replace daily standup meetings with automated status updates. Save 2-3 hours per developer per week.", + }, + alternates: { + canonical: "https://asyncstatus.com", + }, +}; import { BetaMessage } from "../components/beta-message"; import { ConnectCard } from "../components/connect-card"; import { CtaSection } from "../components/cta-section"; @@ -16,6 +43,7 @@ import { TrackCard } from "../components/track-card"; import { UseItYourWay } from "../components/use-it-your-way"; import { peopleSummary } from "./people-summary"; import { PersonSelect } from "./person-select"; +import { StructuredData, organizationSchema, productSchema, websiteSchema, faqSchema } from "../structured-data"; export default async function Page(props: { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; @@ -48,7 +76,7 @@ export default async function Page(props: {
-

AsyncStatus

+ AsyncStatus
@@ -137,21 +165,21 @@ export default async function Page(props: { -

+

Standup meetings suck -

-

+

+

Standup meetings suck -

+ -

+

Your team already pushed code, closed tickets, replied in threads, fixed small things no one asked them to. We turn it into an update. Or you can write it yourself. Either way, no one has to talk about it at 9:30 a.m. -

+

-

+

How it works -

+
@@ -301,13 +329,13 @@ export default async function Page(props: {
-

+

Calculate your savings -

-

+

+

See how much time and money your team can save by replacing synchronous standups with AsyncStatus. - +

@@ -345,6 +373,12 @@ export default async function Page(props: {