6ab52a2499
Backend & Auth: - Add @clerk/nextjs for production authentication - Create src/proxy.ts with clerkMiddleware() for route protection - Implement multi-tenant auth with role-based access control - Add Clerk components (Show, UserButton, SignInButton, SignUpButton) Billing & Payments: - Full Stripe integration (subscriptions, add-ons, customer portal) - Plan tiers: Starter 9/mo, Farm 49/mo, Enterprise 99/mo - Webhook handling for subscription events - createSubscription(), createAddonSubscription(), createCustomerPortalSession() API & Security: - Rate limiting with @upstash/ratelimit (100 req/min API, 20 req/min checkout) - Zod validation schemas for all endpoints (orders, products, campaigns, etc.) - Security headers (CSP, HSTS, X-Frame-Options) - API routes: /api/v1/ with validated, rate-limited endpoints Monitoring: - Sentry error tracking with performance monitoring - PostHog analytics for feature usage, funnels, cohorts - User activity logging and breadcrumb tracking Admin Features: - Analytics dashboard with revenue charts, customer growth, conversion funnel - Onboarding flow with 6-step interactive tour - Referral system with share tracking and reward redemption - Changelog feed with in-app notifications PWA & SEO: - Web app manifest with icons and shortcuts - Service worker for offline support and caching - Full SEO metadata, OpenGraph, Twitter cards - Structured data (JSON-LD) for organization and products Database: - Add referral_codes, changelogs, onboarding_progress tables - Add user_activity_logs, api_keys, notification_preferences - Comprehensive RLS policies for all new tables - Seed data for demo brands and products
218 lines
5.5 KiB
TypeScript
218 lines
5.5 KiB
TypeScript
// SEO and Metadata Utilities
|
|
|
|
import { Metadata } from "next";
|
|
|
|
// Base URL for the application
|
|
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://routecommerce.com";
|
|
|
|
// Default SEO configuration
|
|
export const defaultSEO: Metadata = {
|
|
metadataBase: new URL(BASE_URL),
|
|
title: {
|
|
default: "Route Commerce | Fresh Produce Wholesale Platform",
|
|
template: "%s | Route Commerce",
|
|
},
|
|
description: "Multi-tenant B2B e-commerce platform for fresh produce wholesale distribution. Brands sell to customers who pick up at scheduled stops or receive shipments.",
|
|
keywords: [
|
|
"wholesale produce",
|
|
"farm fresh",
|
|
"B2B e-commerce",
|
|
"produce distribution",
|
|
"pickup stops",
|
|
"fresh produce",
|
|
"farm management",
|
|
"order management",
|
|
"customer portal",
|
|
"wholesale ordering",
|
|
],
|
|
authors: [{ name: "Route Commerce" }],
|
|
creator: "Route Commerce",
|
|
publisher: "Route Commerce",
|
|
robots: {
|
|
index: true,
|
|
follow: true,
|
|
googleBot: {
|
|
index: true,
|
|
follow: true,
|
|
"max-video-preview": -1,
|
|
"max-image-preview": "large",
|
|
"max-snippet": -1,
|
|
},
|
|
},
|
|
openGraph: {
|
|
type: "website",
|
|
locale: "en_US",
|
|
url: BASE_URL,
|
|
siteName: "Route Commerce",
|
|
title: "Route Commerce | Fresh Produce Wholesale Platform",
|
|
description: "Multi-tenant B2B e-commerce platform for fresh produce wholesale distribution.",
|
|
images: [
|
|
{
|
|
url: "/og-image.png",
|
|
width: 1200,
|
|
height: 630,
|
|
alt: "Route Commerce Platform",
|
|
},
|
|
],
|
|
},
|
|
twitter: {
|
|
card: "summary_large_image",
|
|
title: "Route Commerce | Fresh Produce Wholesale Platform",
|
|
description: "Multi-tenant B2B e-commerce platform for fresh produce wholesale distribution.",
|
|
site: "@RouteCommerce",
|
|
creator: "@RouteCommerce",
|
|
images: ["/og-image.png"],
|
|
},
|
|
icons: {
|
|
icon: [
|
|
{ url: "/favicon.svg", type: "image/svg+xml" },
|
|
],
|
|
apple: "/icons/apple-touch-icon.png",
|
|
other: [
|
|
{
|
|
rel: "manifest",
|
|
url: "/manifest.json",
|
|
},
|
|
],
|
|
},
|
|
alternates: {
|
|
canonical: BASE_URL,
|
|
languages: {
|
|
"en-US": BASE_URL,
|
|
},
|
|
},
|
|
};
|
|
|
|
// Brand-specific metadata
|
|
export function getBrandMetadata(brand: {
|
|
name: string;
|
|
slug: string;
|
|
description?: string;
|
|
logo_url?: string;
|
|
}) {
|
|
return {
|
|
title: `${brand.name} | Fresh Produce`,
|
|
description: brand.description || `Order fresh produce from ${brand.name}. Pick up at scheduled stops or get deliveries.`,
|
|
openGraph: {
|
|
title: `${brand.name} | Fresh Produce`,
|
|
description: brand.description || `Order fresh produce from ${brand.name}`,
|
|
url: `${BASE_URL}/${brand.slug}`,
|
|
siteName: brand.name,
|
|
images: brand.logo_url ? [{ url: brand.logo_url }] : [],
|
|
},
|
|
};
|
|
}
|
|
|
|
// Product metadata
|
|
export function getProductMetadata(product: {
|
|
name: string;
|
|
description?: string;
|
|
price?: number;
|
|
image_url?: string;
|
|
}) {
|
|
return {
|
|
title: `${product.name} | Route Commerce`,
|
|
description: product.description || `Order ${product.name} from Route Commerce`,
|
|
openGraph: {
|
|
title: product.name,
|
|
description: product.description,
|
|
images: product.image_url ? [{ url: product.image_url }] : [],
|
|
},
|
|
};
|
|
}
|
|
|
|
// Admin page metadata
|
|
export function getAdminMetadata(pageTitle: string) {
|
|
return {
|
|
title: `${pageTitle} | Admin | Route Commerce`,
|
|
robots: {
|
|
index: false,
|
|
follow: false,
|
|
},
|
|
};
|
|
}
|
|
|
|
// Structured data (JSON-LD)
|
|
export function getOrganizationSchema() {
|
|
return {
|
|
"@context": "https://schema.org",
|
|
"@type": "Organization",
|
|
name: "Route Commerce",
|
|
url: BASE_URL,
|
|
logo: `${BASE_URL}/logo.svg`,
|
|
sameAs: [
|
|
"https://twitter.com/RouteCommerce",
|
|
"https://linkedin.com/company/route-commerce",
|
|
],
|
|
contactPoint: {
|
|
"@type": "ContactPoint",
|
|
contactType: "customer service",
|
|
email: "support@routecommerce.com",
|
|
},
|
|
};
|
|
}
|
|
|
|
export function getProductSchema(product: {
|
|
name: string;
|
|
description: string;
|
|
price: number;
|
|
currency?: string;
|
|
availability?: string;
|
|
image?: string;
|
|
brand?: string;
|
|
}) {
|
|
return {
|
|
"@context": "https://schema.org",
|
|
"@type": "Product",
|
|
name: product.name,
|
|
description: product.description,
|
|
image: product.image,
|
|
brand: {
|
|
"@type": "Brand",
|
|
name: product.brand || "Route Commerce",
|
|
},
|
|
offers: {
|
|
"@type": "Offer",
|
|
price: product.price,
|
|
priceCurrency: product.currency || "USD",
|
|
availability: product.availability || "https://schema.org/InStock",
|
|
},
|
|
};
|
|
}
|
|
|
|
export function getLocalBusinessSchema(business: {
|
|
name: string;
|
|
address: string;
|
|
city: string;
|
|
state: string;
|
|
postalCode: string;
|
|
phone?: string;
|
|
email?: string;
|
|
openingHours?: string[];
|
|
}) {
|
|
return {
|
|
"@context": "https://schema.org",
|
|
"@type": "LocalBusiness",
|
|
name: business.name,
|
|
address: {
|
|
"@type": "PostalAddress",
|
|
streetAddress: business.address,
|
|
addressLocality: business.city,
|
|
addressRegion: business.state,
|
|
postalCode: business.postalCode,
|
|
},
|
|
telephone: business.phone,
|
|
email: business.email,
|
|
openingHours: business.openingHours,
|
|
};
|
|
}
|
|
|
|
// Generate sitemap data
|
|
export function generateSitemap(pages: { url: string; lastModified?: Date; changeFrequency?: string; priority?: number }[]) {
|
|
return pages.map((page) => ({
|
|
url: `${BASE_URL}${page.url}`,
|
|
lastModified: page.lastModified || new Date(),
|
|
changeFrequency: page.changeFrequency || "weekly",
|
|
priority: page.priority || 0.5,
|
|
}));
|
|
} |