From adf78bb2ee99e6e293faa2ad395c93ac34f5f521 Mon Sep 17 00:00:00 2001
From: Arnas Donauskas <61235555+ArnasDon@users.noreply.github.com>
Date: Sun, 19 Apr 2026 12:15:42 +0300
Subject: [PATCH] =?UTF-8?q?Revert=20"feat(seo):=20full=20SEO=20pass=20?=
=?UTF-8?q?=E2=80=94=20metadata,=20structured=20data,=20sitemap,=20robots"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app/(auth)/layout.tsx | 24 ------
src/app/(dashboard)/dashboard-shell.tsx | 53 ------------
src/app/(dashboard)/layout.tsx | 65 +++++++++-----
src/app/layout.tsx | 78 +----------------
src/app/opengraph-image.tsx | 103 ----------------------
src/app/page.tsx | 20 +----
src/app/robots.ts | 35 --------
src/app/sitemap.ts | 20 -----
src/components/landing/faq.tsx | 33 ++++++-
src/components/seo/json-ld.tsx | 22 -----
src/lib/seo/faq-data.ts | 36 --------
src/lib/seo/site-config.ts | 56 ------------
src/lib/seo/structured-data.ts | 110 ------------------------
13 files changed, 82 insertions(+), 573 deletions(-)
delete mode 100644 src/app/(auth)/layout.tsx
delete mode 100644 src/app/(dashboard)/dashboard-shell.tsx
delete mode 100644 src/app/opengraph-image.tsx
delete mode 100644 src/app/robots.ts
delete mode 100644 src/app/sitemap.ts
delete mode 100644 src/components/seo/json-ld.tsx
delete mode 100644 src/lib/seo/faq-data.ts
delete mode 100644 src/lib/seo/site-config.ts
delete mode 100644 src/lib/seo/structured-data.ts
diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx
deleted file mode 100644
index 63fe3c1..0000000
--- a/src/app/(auth)/layout.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { Metadata } from "next";
-import type { ReactNode } from "react";
-
-// Shared metadata for auth pages (login / signup / forgot-password).
-// None of these should be indexed — they'd compete with the marketing
-// landing in SERPs and offer nothing to a searcher who hasn't already
-// signed up. Each page still gets its own
via its own
-// metadata.title override below the route group layout.
-export const metadata: Metadata = {
- robots: {
- index: false,
- follow: false,
- nocache: true,
- googleBot: {
- index: false,
- follow: false,
- noimageindex: true,
- },
- },
-};
-
-export default function AuthLayout({ children }: { children: ReactNode }) {
- return children;
-}
diff --git a/src/app/(dashboard)/dashboard-shell.tsx b/src/app/(dashboard)/dashboard-shell.tsx
deleted file mode 100644
index 68c4d51..0000000
--- a/src/app/(dashboard)/dashboard-shell.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-"use client";
-
-import { useEffect } from "react";
-import { useRouter } from "next/navigation";
-import { AuthProvider, useAuth } from "@/hooks/use-auth";
-import { Sidebar } from "@/components/layout/sidebar";
-import { Header } from "@/components/layout/header";
-
-// Auth-gated dashboard shell. Extracted from the layout so the layout
-// itself can stay a server component and export metadata (noindex) —
-// client components can't export Next's metadata object.
-
-function DashboardShellInner({ children }: { children: React.ReactNode }) {
- const { user, loading } = useAuth();
- const router = useRouter();
-
- useEffect(() => {
- if (!loading && !user) {
- router.push("/login");
- }
- }, [user, loading, router]);
-
- if (loading) {
- return (
-
- );
- }
-
- if (!user) return null;
-
- return (
-
- );
-}
-
-export function DashboardShell({ children }: { children: React.ReactNode }) {
- return (
-
- {children}
-
- );
-}
diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx
index f6d516c..c70f00a 100644
--- a/src/app/(dashboard)/layout.tsx
+++ b/src/app/(dashboard)/layout.tsx
@@ -1,28 +1,53 @@
-import type { Metadata } from "next";
-import { DashboardShell } from "./dashboard-shell";
+"use client";
-// Server layout whose only job is to declare "do not index" metadata
-// for the authed app. robots.ts already disallows these paths at the
-// crawler-level and middleware redirects unauthenticated visitors, so
-// this is belt-and-suspenders — but SEO-critical if a URL ever leaks
-// via a link shared externally.
-export const metadata: Metadata = {
- robots: {
- index: false,
- follow: false,
- nocache: true,
- googleBot: {
- index: false,
- follow: false,
- noimageindex: true,
- },
- },
-};
+import { useEffect } from "react";
+import { useRouter } from "next/navigation";
+import { AuthProvider, useAuth } from "@/hooks/use-auth";
+import { Sidebar } from "@/components/layout/sidebar";
+import { Header } from "@/components/layout/header";
+
+function DashboardShell({ children }: { children: React.ReactNode }) {
+ const { user, loading } = useAuth();
+ const router = useRouter();
+
+ useEffect(() => {
+ if (!loading && !user) {
+ router.push("/login");
+ }
+ }, [user, loading, router]);
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (!user) return null;
+
+ return (
+
+ );
+}
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
- return {children};
+ return (
+
+ {children}
+
+ );
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 67b9c8b..d77e8c9 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,88 +1,16 @@
-import type { Metadata, Viewport } from "next";
+import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { Toaster } from "sonner";
import "./globals.css";
-import {
- OG_IMAGE_ALT,
- SITE_DESCRIPTION,
- SITE_KEYWORDS,
- SITE_NAME,
- SITE_TAGLINE,
- SITE_URL,
-} from "@/lib/seo/site-config";
const inter = Inter({
variable: "--font-sans",
subsets: ["latin"],
});
-// Consolidated site-wide metadata. Page-specific metadata (landing,
-// auth pages, etc.) override the narrower fields (title, description,
-// robots); defaults defined here are inherited everywhere else so we
-// never ship a page without OpenGraph / Twitter coverage.
export const metadata: Metadata = {
- metadataBase: new URL(SITE_URL),
- title: {
- default: `${SITE_NAME} — ${SITE_TAGLINE}`,
- template: `%s — ${SITE_NAME}`,
- },
- description: SITE_DESCRIPTION,
- keywords: SITE_KEYWORDS,
- applicationName: SITE_NAME,
- authors: [{ name: SITE_NAME, url: SITE_URL }],
- creator: SITE_NAME,
- publisher: SITE_NAME,
- category: "Business Software",
- alternates: {
- canonical: "/",
- },
- openGraph: {
- type: "website",
- siteName: SITE_NAME,
- title: `${SITE_NAME} — ${SITE_TAGLINE}`,
- description: SITE_DESCRIPTION,
- url: SITE_URL,
- locale: "en_US",
- images: [
- {
- url: "/opengraph-image",
- width: 1200,
- height: 630,
- alt: OG_IMAGE_ALT,
- },
- ],
- },
- twitter: {
- card: "summary_large_image",
- title: `${SITE_NAME} — ${SITE_TAGLINE}`,
- description: SITE_DESCRIPTION,
- images: ["/opengraph-image"],
- },
- robots: {
- index: true,
- follow: true,
- googleBot: {
- index: true,
- follow: true,
- "max-image-preview": "large",
- "max-snippet": -1,
- },
- },
- icons: {
- icon: [{ url: "/icon" }],
- shortcut: ["/favicon.ico"],
- },
- formatDetection: {
- email: false,
- address: false,
- telephone: false,
- },
-};
-
-// Dark theme color for the mobile browser chrome.
-export const viewport: Viewport = {
- themeColor: "#020617",
- colorScheme: "dark",
+ title: "WaCRM - WhatsApp CRM",
+ description: "WhatsApp CRM for managing conversations, contacts, and pipelines",
};
export default function RootLayout({
diff --git a/src/app/opengraph-image.tsx b/src/app/opengraph-image.tsx
deleted file mode 100644
index 8e0c45f..0000000
--- a/src/app/opengraph-image.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { ImageResponse } from 'next/og'
-import { SITE_NAME, SITE_TAGLINE } from '@/lib/seo/site-config'
-
-/**
- * Auto-generated OG image served at /opengraph-image.
- * Next renders this JSX to a PNG at build time, caches it, and wires
- * the URL into the default `` for every
- * page that inherits the root metadata.
- *
- * Facebook / LinkedIn / Slack / Twitter all fetch this when the link
- * is shared, so keep it readable at thumbnail sizes (no tiny text,
- * high contrast).
- */
-
-export const alt = `${SITE_NAME} — ${SITE_TAGLINE}`
-export const size = { width: 1200, height: 630 }
-export const contentType = 'image/png'
-
-export default async function Image() {
- return new ImageResponse(
- (
-
-
-
- W
-
-
- {SITE_NAME}
-
-
-
-
- Run your WhatsApp business from{' '}
- one inbox.
-
-
-
- Shared inbox, sales pipelines, broadcasts, and no-code automations —
- built on the official WhatsApp Business API.
-
-
-
- ✓ Shared inbox
- ✓ Automations
- ✓ Pipelines
- ✓ Analytics
-
-
- ),
- size,
- )
-}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 4de18f9..4e4e79b 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -12,22 +12,11 @@ import { InboxMock } from '@/components/landing/mock/inbox-mock'
import { PipelineMock } from '@/components/landing/mock/pipeline-mock'
import { AutomationMock } from '@/components/landing/mock/automation-mock'
import { AnalyticsMock } from '@/components/landing/mock/analytics-mock'
-import { JsonLd } from '@/components/seo/json-ld'
-import { landingPageLd } from '@/lib/seo/structured-data'
-import { SITE_DESCRIPTION, SITE_NAME, SITE_TAGLINE } from '@/lib/seo/site-config'
-// Landing-specific metadata. Most fields are inherited from the root
-// layout's metadata — we override title (so the hero copy shows in
-// SERPs / tab titles) and set an explicit canonical for "/" to avoid
-// trailing-slash duplicate-content signals.
export const metadata: Metadata = {
- title: {
- absolute: `${SITE_NAME} — ${SITE_TAGLINE}`,
- },
- description: SITE_DESCRIPTION,
- alternates: {
- canonical: '/',
- },
+ title: 'WaCRM — Run your WhatsApp business from one inbox',
+ description:
+ 'Shared inbox, contact hub, sales pipelines, broadcasts, and no-code automations for WhatsApp. Built on the official WhatsApp Business API.',
}
// Marketing landing. Visible at / for everyone (auth redirect that
@@ -36,9 +25,6 @@ export const metadata: Metadata = {
export default function LandingPage() {
return (
- {/* JSON-LD — WebSite, Organization, SoftwareApplication, FAQPage.
- Emitted before any visible content so crawlers hit it first. */}
-
diff --git a/src/app/robots.ts b/src/app/robots.ts
deleted file mode 100644
index ad1e62e..0000000
--- a/src/app/robots.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import type { MetadataRoute } from 'next'
-import { SITE_URL } from '@/lib/seo/site-config'
-
-/**
- * robots.txt served at /robots.txt. We explicitly disallow every
- * gated area so crawlers don't spend their budget on pages they'll
- * bounce off. Auth pages are closed off too — no SEO value, and
- * we don't want "Sign in to WaCRM" competing with the landing page
- * in SERPs.
- */
-export default function robots(): MetadataRoute.Robots {
- return {
- rules: [
- {
- userAgent: '*',
- allow: '/',
- disallow: [
- '/api/',
- '/dashboard',
- '/inbox',
- '/contacts',
- '/pipelines',
- '/broadcasts',
- '/automations',
- '/settings',
- '/login',
- '/signup',
- '/forgot-password',
- ],
- },
- ],
- sitemap: `${SITE_URL}/sitemap.xml`,
- host: SITE_URL,
- }
-}
diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts
deleted file mode 100644
index b387557..0000000
--- a/src/app/sitemap.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { MetadataRoute } from 'next'
-import { SITE_URL } from '@/lib/seo/site-config'
-
-/**
- * Sitemap served at /sitemap.xml. Only lists public, indexable pages
- * — auth pages are explicitly noindex, and every route under /app is
- * gated. The landing anchors (#features, #how-it-works, #faq) don't
- * belong here; fragments aren't a sitemap concept.
- */
-export default function sitemap(): MetadataRoute.Sitemap {
- const now = new Date()
- return [
- {
- url: `${SITE_URL}/`,
- lastModified: now,
- changeFrequency: 'weekly',
- priority: 1,
- },
- ]
-}
diff --git a/src/components/landing/faq.tsx b/src/components/landing/faq.tsx
index e964783..c58ca00 100644
--- a/src/components/landing/faq.tsx
+++ b/src/components/landing/faq.tsx
@@ -3,9 +3,38 @@
import { useState } from 'react'
import { ChevronDown } from 'lucide-react'
import { Section, SectionHeader } from './section'
-import { FAQ_ITEMS } from '@/lib/seo/faq-data'
import { cn } from '@/lib/utils'
+// Answering real objections a small-business owner has before signing
+// up. Order matters — setup friction is usually the #1 concern, cost
+// #2, data ownership #3.
+const QA = [
+ {
+ q: 'Do I need my own WhatsApp Business API access?',
+ a: "Yes. WaCRM plugs into your existing Meta WhatsApp Business setup — you bring the phone number and access token, we provide the CRM tooling around it. Any Meta-approved BSP (Business Solution Provider) works.",
+ },
+ {
+ q: 'Can my whole team share one WhatsApp number?',
+ a: 'Yes. Assign conversations to specific agents, track who is responding to what, and hand off threads without losing context. All your agents work from a single shared inbox.',
+ },
+ {
+ q: 'How fast is setup?',
+ a: 'Most teams are live in under 30 minutes once their WhatsApp Business number has been approved by Meta. Paste your credentials in Settings, import contacts if you have them, and start replying.',
+ },
+ {
+ q: 'Who owns the data?',
+ a: 'You do. Everything lives in your own Supabase project — contacts, conversations, deals, automation logs. Export it anytime; there is no lock-in on the data layer.',
+ },
+ {
+ q: 'Can I send bulk messages and automated replies?',
+ a: 'Yes. Broadcasts send Meta-approved templates to segmented contact lists with delivery tracking. Automations run no-code flows triggered by new contacts, keywords, tag changes, and more.',
+ },
+ {
+ q: 'What about message templates?',
+ a: 'Templates you create in Meta are synced automatically. Use them from the inbox, broadcasts, or inside an automation step.',
+ },
+]
+
export function FAQ() {
const [openIdx, setOpenIdx] = useState(0)
return (
@@ -17,7 +46,7 @@ export function FAQ() {
/>
- {FAQ_ITEMS.map((item, i) => {
+ {QA.map((item, i) => {
const isOpen = openIdx === i
return (
diff --git a/src/components/seo/json-ld.tsx b/src/components/seo/json-ld.tsx
deleted file mode 100644
index a544b0f..0000000
--- a/src/components/seo/json-ld.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Emits one