mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-29 10:56:24 +02:00
feat: enhance SurfSense with new skills, blog section, and improve SEO metadata
Some checks failed
Build and Push Docker Images / tag_release (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend) (push) Has been cancelled
Build and Push Docker Images / create_manifest (web, surfsense-web) (push) Has been cancelled
Some checks failed
Build and Push Docker Images / tag_release (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend) (push) Has been cancelled
Build and Push Docker Images / create_manifest (web, surfsense-web) (push) Has been cancelled
- Added multiple new skills to skills-lock.json from the repository `aaron-he-zhu/seo-geo-claude-skills`. - Introduced `fuzzy-search` dependency in package.json for improved search functionality. - Updated pnpm-lock.yaml to include the new `fuzzy-search` package. - Enhanced SEO metadata across various pages, including canonical links and descriptions for better search visibility. - Improved layout and structure of several components, including the homepage and changelog, to enhance user experience.
This commit is contained in:
parent
61b3f0d7e3
commit
7ea840dbb2
120 changed files with 25729 additions and 352 deletions
|
|
@ -15,7 +15,7 @@ export const Logo = ({
|
|||
<Image
|
||||
src="/icon-128.svg"
|
||||
className={cn("select-none dark:invert", className)}
|
||||
alt="logo"
|
||||
alt="SurfSense"
|
||||
width={128}
|
||||
height={128}
|
||||
priority={priority}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {
|
|||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import type { AnnouncementCategory } from "@/contracts/types/announcement.types";
|
||||
import type { AnnouncementWithState } from "@/hooks/use-announcements";
|
||||
|
|
@ -67,7 +66,7 @@ export function AnnouncementCard({ announcement }: { announcement: AnnouncementW
|
|||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<CardTitle className="text-base leading-tight">{announcement.title}</CardTitle>
|
||||
<h2 className="text-base font-semibold leading-tight tracking-tight">{announcement.title}</h2>
|
||||
<Badge variant={config.badgeVariant} className="text-[10px] px-1.5 py-0">
|
||||
{config.label}
|
||||
</Badge>
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ export function ContactFormGridWithDetails() {
|
|||
<IconMailFilled className="h-6 w-6 text-blue-500" />
|
||||
</FeatureIconContainer>
|
||||
</div>
|
||||
<h2 className="mt-9 bg-gradient-to-b from-neutral-800 to-neutral-900 bg-clip-text text-center text-xl font-bold text-transparent md:text-3xl lg:text-5xl dark:from-neutral-200 dark:to-neutral-300">
|
||||
Contact
|
||||
</h2>
|
||||
<h1 className="mt-9 bg-gradient-to-b from-neutral-800 to-neutral-900 bg-clip-text text-center text-xl font-bold text-transparent md:text-3xl lg:text-5xl dark:from-neutral-200 dark:to-neutral-300">
|
||||
Contact
|
||||
</h1>
|
||||
<p className="mt-8 max-w-lg text-center text-base text-neutral-600 dark:text-neutral-400">
|
||||
We'd love to hear from you!
|
||||
</p>
|
||||
|
|
|
|||
11
surfsense_web/components/container.tsx
Normal file
11
surfsense_web/components/container.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { cn } from "@/lib/utils";
|
||||
|
||||
export function Container({
|
||||
className,
|
||||
children,
|
||||
}: {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <div className={cn("mx-auto max-w-7xl px-4 sm:px-6 lg:px-8", className)}>{children}</div>;
|
||||
}
|
||||
17
surfsense_web/components/homepage/auth-redirect.tsx
Normal file
17
surfsense_web/components/homepage/auth-redirect.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { getBearerToken } from "@/lib/auth-utils";
|
||||
|
||||
export function AuthRedirect() {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (getBearerToken()) {
|
||||
router.replace("/dashboard");
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
@ -25,6 +25,10 @@ export function FooterNew() {
|
|||
title: "Pricing",
|
||||
href: "/pricing",
|
||||
},
|
||||
{
|
||||
title: "Blog",
|
||||
href: "/blog",
|
||||
},
|
||||
{
|
||||
title: "Docs",
|
||||
href: "/docs",
|
||||
|
|
|
|||
|
|
@ -153,14 +153,13 @@ export function HeroSection() {
|
|||
</h1>
|
||||
<div className="mt-4 flex w-full flex-col items-start justify-between gap-4 md:mt-12 md:flex-row md:items-end md:gap-10">
|
||||
<div>
|
||||
<h2
|
||||
className={cn(
|
||||
"relative mb-8 max-w-2xl text-left text-sm tracking-wide text-neutral-600 antialiased sm:text-base md:text-xl dark:text-neutral-400"
|
||||
)}
|
||||
>
|
||||
An open source, privacy focused alternative to NotebookLM for teams with no data
|
||||
limits.
|
||||
</h2>
|
||||
<p
|
||||
className={cn(
|
||||
"relative mb-8 max-w-2xl text-left text-sm tracking-wide text-neutral-600 antialiased sm:text-base md:text-xl dark:text-neutral-400"
|
||||
)}
|
||||
>
|
||||
A free, open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude AI, and any AI model for free.
|
||||
</p>
|
||||
|
||||
<div className="relative mb-4 flex w-full flex-col justify-center gap-y-2 sm:flex-row sm:justify-start sm:space-y-0 sm:space-x-4">
|
||||
<DownloadButton />
|
||||
|
|
|
|||
|
|
@ -180,11 +180,11 @@ export default function ExternalIntegrations() {
|
|||
>
|
||||
{/* Heading */}
|
||||
<div className="text-center mb-12 md:mb-16 relative z-20 px-4">
|
||||
<h3 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold text-gray-900 dark:text-white leading-[1.1] tracking-tight">
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold text-gray-900 dark:text-white leading-[1.1] tracking-tight">
|
||||
Integrate with your
|
||||
<br />
|
||||
team's most important tools
|
||||
</h3>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Scrolling columns container — masked at edges so the page background shows through seamlessly */}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export const Navbar = ({ scrolledBgClassName }: NavbarProps = {}) => {
|
|||
|
||||
const navItems = [
|
||||
{ name: "Pricing", link: "/pricing" },
|
||||
{ name: "Blog", link: "/blog" },
|
||||
{ name: "Changelog", link: "/changelog" },
|
||||
{ name: "Docs", link: "/docs" },
|
||||
{ name: "Contact\u00A0Us", link: "/contact" },
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export function Pricing({
|
|||
return (
|
||||
<div className="container mx-auto py-20">
|
||||
<div className="text-center space-y-4 mb-12">
|
||||
<h2 className="text-4xl font-bold tracking-tight sm:text-5xl">{title}</h2>
|
||||
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl">{title}</h1>
|
||||
<p className="text-muted-foreground text-lg whitespace-pre-line">{description}</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
52
surfsense_web/components/seo/breadcrumb-nav.tsx
Normal file
52
surfsense_web/components/seo/breadcrumb-nav.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { ChevronRight } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { BreadcrumbJsonLd } from "./json-ld";
|
||||
|
||||
interface BreadcrumbItem {
|
||||
name: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
interface BreadcrumbNavProps {
|
||||
items: BreadcrumbItem[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function BreadcrumbNav({ items, className }: BreadcrumbNavProps) {
|
||||
const jsonLdItems = items.map((item) => ({
|
||||
name: item.name,
|
||||
url: `https://surfsense.com${item.href}`,
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<BreadcrumbJsonLd items={jsonLdItems} />
|
||||
<nav aria-label="Breadcrumb" className={className}>
|
||||
<ol className="flex items-center gap-1.5 text-sm text-muted-foreground">
|
||||
{items.map((item, index) => {
|
||||
const isLast = index === items.length - 1;
|
||||
return (
|
||||
<li key={item.href} className="flex items-center gap-1.5">
|
||||
{index > 0 && (
|
||||
<ChevronRight className="h-3.5 w-3.5 shrink-0" aria-hidden />
|
||||
)}
|
||||
{isLast ? (
|
||||
<span className="font-medium text-foreground" aria-current="page">
|
||||
{item.name}
|
||||
</span>
|
||||
) : (
|
||||
<Link
|
||||
href={item.href}
|
||||
className="transition-colors hover:text-foreground"
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
</nav>
|
||||
</>
|
||||
);
|
||||
}
|
||||
186
surfsense_web/components/seo/json-ld.tsx
Normal file
186
surfsense_web/components/seo/json-ld.tsx
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
interface JsonLdProps {
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export function JsonLd({ data }: JsonLdProps) {
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function OrganizationJsonLd() {
|
||||
return (
|
||||
<JsonLd
|
||||
data={{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
name: "SurfSense",
|
||||
url: "https://surfsense.com",
|
||||
logo: "https://surfsense.com/logo.png",
|
||||
description:
|
||||
"Open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude AI, and any AI model for free.",
|
||||
sameAs: [
|
||||
"https://github.com/MODSetter/SurfSense",
|
||||
"https://discord.gg/Cg2M4GUJ",
|
||||
],
|
||||
contactPoint: {
|
||||
"@type": "ContactPoint",
|
||||
email: "rohan@surfsense.com",
|
||||
contactType: "sales",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function WebSiteJsonLd() {
|
||||
return (
|
||||
<JsonLd
|
||||
data={{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
name: "SurfSense",
|
||||
url: "https://surfsense.com",
|
||||
description:
|
||||
"Open source NotebookLM alternative for teams with no data limits. Free ChatGPT, Claude AI, and any AI model.",
|
||||
potentialAction: {
|
||||
"@type": "SearchAction",
|
||||
target: {
|
||||
"@type": "EntryPoint",
|
||||
urlTemplate: "https://surfsense.com/docs?search={search_term_string}",
|
||||
},
|
||||
"query-input": "required name=search_term_string",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SoftwareApplicationJsonLd() {
|
||||
return (
|
||||
<JsonLd
|
||||
data={{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
name: "SurfSense",
|
||||
applicationCategory: "BusinessApplication",
|
||||
operatingSystem: "Windows, macOS, Linux, Web",
|
||||
offers: {
|
||||
"@type": "Offer",
|
||||
price: "0",
|
||||
priceCurrency: "USD",
|
||||
description: "Free plan with 500 pages included",
|
||||
},
|
||||
description:
|
||||
"Open source NotebookLM alternative with free access to ChatGPT, Claude AI, and any model. Connect Slack, Google Drive, Notion, Confluence, GitHub, and dozens more data sources.",
|
||||
url: "https://surfsense.com",
|
||||
downloadUrl: "https://github.com/MODSetter/SurfSense/releases",
|
||||
featureList: [
|
||||
"Free access to ChatGPT, Claude AI, and any AI model",
|
||||
"AI-powered semantic search across all connected tools",
|
||||
"Federated search across Slack, Google Drive, Notion, Confluence, GitHub",
|
||||
"No data limits with open source self-hosting",
|
||||
"Real-time collaborative team chats",
|
||||
"Document Q&A with citations",
|
||||
"Report generation",
|
||||
"Podcast and video generation from sources",
|
||||
"Enterprise knowledge management",
|
||||
"Self-hostable and privacy-focused",
|
||||
],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArticleJsonLd({
|
||||
title,
|
||||
description,
|
||||
url,
|
||||
datePublished,
|
||||
author,
|
||||
image,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
url: string;
|
||||
datePublished: string;
|
||||
author: string;
|
||||
image?: string;
|
||||
}) {
|
||||
return (
|
||||
<JsonLd
|
||||
data={{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
headline: title,
|
||||
description,
|
||||
url,
|
||||
datePublished,
|
||||
author: {
|
||||
"@type": "Organization",
|
||||
name: author,
|
||||
},
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "SurfSense",
|
||||
logo: {
|
||||
"@type": "ImageObject",
|
||||
url: "https://surfsense.com/logo.png",
|
||||
},
|
||||
},
|
||||
image: image || "https://surfsense.com/og-image.png",
|
||||
mainEntityOfPage: {
|
||||
"@type": "WebPage",
|
||||
"@id": url,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function BreadcrumbJsonLd({
|
||||
items,
|
||||
}: {
|
||||
items: { name: string; url: string }[];
|
||||
}) {
|
||||
return (
|
||||
<JsonLd
|
||||
data={{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
itemListElement: items.map((item, index) => ({
|
||||
"@type": "ListItem",
|
||||
position: index + 1,
|
||||
name: item.name,
|
||||
item: item.url,
|
||||
})),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function FAQJsonLd({
|
||||
questions,
|
||||
}: {
|
||||
questions: { question: string; answer: string }[];
|
||||
}) {
|
||||
return (
|
||||
<JsonLd
|
||||
data={{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "FAQPage",
|
||||
mainEntity: questions.map((q) => ({
|
||||
"@type": "Question",
|
||||
name: q.question,
|
||||
acceptedAnswer: {
|
||||
"@type": "Answer",
|
||||
text: q.answer,
|
||||
},
|
||||
})),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue