fix: content audit — SEO, cross-links, draft filtering, styling

- Add sitemap.xml and robots.txt (such.software-site)
- Fix blog date on neroswap post (2025→2026)
- Filter draft posts from generateStaticParams and getPost
- Fix stale product list in website-relaunch blog post
- Fix Godot version (4.6→4)
- Add cross-links: product pages→blog posts, blog posts→product pages
- Add OG/Twitter metadata to blog [slug] pages
- Fix Bauhaus Echo privacy: hover color (emerald→blue), contact URL
- Fix Suchoice: card styling (p-6→glass-card), font-semibold→font-bold
- Fix products listing meta to include Suchoice
This commit is contained in:
jwinterm
2026-03-17 13:17:59 -04:00
parent 931f80abdf
commit d0a3e8c399
14 changed files with 76 additions and 11 deletions
@@ -32,3 +32,5 @@ Built with [libGDX](https://libgdx.com/), a cross-platform Java game framework.
- **Mobile (free with ads)** — [App Store](https://apps.apple.com/us/app/bauhaus-echo/id6759010657) | [Google Play](https://play.google.com/store/apps/details?id=com.suchsoftware.bauhausecho)
- **Desktop ($2, no ads)** — [itch.io](https://suchsoftware.itch.io/bauhaus-echo)
- **Web (free)** — [bauhaus.such.software](https://bauhaus.such.software)
See the full [Bauhaus Echo product page](/products/bauhaus-echo) for more details.
@@ -1,6 +1,6 @@
---
title: "Neroswap: a Privacy-First DEX & CEX Aggregator"
date: "2025-06-28"
date: "2026-03-17"
excerpt: "How we built a real-time orderbook aggregator across 6 decentralized and centralized exchanges — with no KYC, no custody, and no tracking."
---
@@ -57,3 +57,5 @@ All the DEX sources (BasicSwap, Eigenwallet) are fully decentralized with on-cha
Visit [neroswap.com](https://neroswap.com) to see the live dashboard.
The project is built and maintained by [Such Software](/#contact). If you need a custom aggregator, scraping pipeline, or privacy-first web application, [get in touch](/#contact).
See the full [Neroswap product page](/products/neroswap) for the complete feature list and exchange descriptions.
@@ -29,6 +29,8 @@ Your private spend keys are generated locally and never leave your browser. They
Our initial submission was rejected for requesting an unused permission (activeTab). We had included it for a planned feature that was not yet implemented. The fix is already in v0.1.6. We have removed the permission and resubmitted.
See the full [Smirk Wallet product page](/products/smirk-wallet) for more details.
In the meantime, you can install manually from [GitHub Releases](https://github.com/Such-Software/smirk-extension/releases). Firefox support is also available.
## Open Source
@@ -31,4 +31,4 @@ The NebulaField component is lazy loaded with next/dynamic and requestIdleCallba
## Structure
The site now has Products, Services, Blog, and About sections. Products showcases our standalone offerings: Webshops, Custom Websites, Smirk Wallet, and Bauhaus Echo. Services covers our partnership model and consulting work. Mobile users get a bottom navigation bar.
The site now has Products, Services, Blog, and About sections. Products showcases our standalone offerings: Neroswap, Webshops, Custom Websites, Smirk Wallet, Bauhaus Echo, Suchoice, and Wownero Moon Launch. Services covers our partnership model and consulting work. Mobile users get a bottom navigation bar.
@@ -41,7 +41,7 @@ Between levels, you spend Moonrocks (earned by collecting crypto pickups) in the
## Tech Stack
Built with [Godot 4.6](https://godotengine.org/) in pure GDScript. The backend (leaderboards, cloud save) runs on our own infrastructure at api.such.software. Ads use AdMob on mobile and AdSense on web, with a "Remove Ads" purchase option. Desktop builds are always ad-free.
Built with [Godot 4](https://godotengine.org/) in pure GDScript. The backend (leaderboards, cloud save) runs on our own infrastructure at api.such.software. Ads use AdMob on mobile and AdSense on web, with a "Remove Ads" purchase option. Desktop builds are always ad-free.
## Get It
+12
View File
@@ -20,6 +20,18 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
title: `${post.title} | Such Software Blog`,
description: post.excerpt,
alternates: { canonical: `/blog/${slug}` },
openGraph: {
title: post.title,
description: post.excerpt,
type: "article",
url: `/blog/${slug}`,
siteName: "Such Software",
},
twitter: {
card: "summary",
title: post.title,
description: post.excerpt,
},
};
}
@@ -100,6 +100,10 @@ export default function BauhausEchoPage() {
Privacy Policy
</Link>
<span>•</span>
<Link href="/blog/bauhaus-echo-launch" className="hover:text-foreground transition-colors">
Blog Post
</Link>
<span>•</span>
<Link href="/support" className="hover:text-foreground transition-colors">
Support &amp; Feedback
</Link>
@@ -14,7 +14,7 @@ export default function BauhausEchoPrivacyPage() {
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<Header />
<div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/products/bauhaus-echo" className="text-sm text-muted-foreground hover:text-emerald-600 dark:hover:text-emerald-400 mb-8 inline-flex items-center gap-2 transition-colors">
<Link href="/products/bauhaus-echo" className="text-sm text-muted-foreground hover:text-blue-600 dark:hover:text-blue-400 mb-8 inline-flex items-center gap-2 transition-colors">
← Back to Bauhaus Echo
</Link>
@@ -253,8 +253,8 @@ export default function BauhausEchoPrivacyPage() {
<strong>Such Software LLC</strong><br />
Email: apps@such.software<br />
Website:{" "}
<a href="https://bauhaus.such.software" target="_blank" rel="noopener noreferrer">
https://bauhaus.such.software
<a href="https://such.software" target="_blank" rel="noopener noreferrer">
https://such.software
</a>
</p>
+1 -1
View File
@@ -6,7 +6,7 @@ import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Products | Such Software",
description: "Tools and applications built by Such Software. Neroswap, Webshops, custom websites, Smirk Wallet, Bauhaus Echo, and Wownero Moon Launch.",
description: "Tools and applications built by Such Software. Neroswap, Webshops, Custom Websites, Smirk Wallet, Bauhaus Echo, Suchoice, and Wownero Moon Launch.",
alternates: { canonical: "/products" },
openGraph: {
title: "Products | Such Software",
@@ -112,6 +112,9 @@ export default function SmirkWalletPage() {
</div>
<div className="flex gap-4">
<Link href="/blog/smirk-wallet-release">
<Button>Read the Blog Post</Button>
</Link>
<a href="https://t.me/smirkwallet" target="_blank" rel="noopener noreferrer">
<Button variant="outline">Telegram</Button>
</a>
+4 -4
View File
@@ -46,18 +46,18 @@ export default function SuchoicePage() {
</Button>
</div>
<h2 className="text-2xl font-semibold mb-6">Features</h2>
<h2 className="text-2xl font-bold mb-6">Features</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-12">
{features.map((feature) => (
<Card key={feature.name} className="p-6">
<h3 className="text-xl font-medium mb-2">{feature.name}</h3>
<Card key={feature.name} className="glass-card border-pink-500/20">
<h3 className="text-xl font-bold mb-2">{feature.name}</h3>
<p className="text-muted-foreground">{feature.description}</p>
</Card>
))}
</div>
<div className="mt-16 pt-8 border-t border-border">
<h2 className="text-2xl font-semibold mb-4">Legal</h2>
<h2 className="text-2xl font-bold mb-4">Legal</h2>
<ul className="space-y-2">
<li>
<Link href="/products/suchoice/privacy" className="text-blue-600 dark:text-blue-400 hover:underline">
+8
View File
@@ -0,0 +1,8 @@
import type { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots {
return {
rules: { userAgent: "*", allow: "/" },
sitemap: "https://such.software/sitemap.xml",
};
}
+26
View File
@@ -0,0 +1,26 @@
import type { MetadataRoute } from "next";
export default function sitemap(): MetadataRoute.Sitemap {
const base = "https://such.software";
return [
{ url: base, lastModified: new Date(), changeFrequency: "weekly", priority: 1.0 },
{ url: `${base}/products`, lastModified: new Date(), changeFrequency: "weekly", priority: 0.9 },
{ url: `${base}/products/neroswap`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.8 },
{ url: `${base}/products/webshops`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.8 },
{ url: `${base}/products/custom-websites`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.8 },
{ url: `${base}/products/smirk-wallet`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.8 },
{ url: `${base}/products/bauhaus-echo`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.8 },
{ url: `${base}/products/suchoice`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.7 },
{ url: `${base}/products/wownero-moon-launch`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.7 },
{ url: `${base}/services`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.9 },
{ url: `${base}/about`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.7 },
{ url: `${base}/support`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.5 },
{ url: `${base}/blog`, lastModified: new Date(), changeFrequency: "weekly", priority: 0.8 },
{ url: `${base}/blog/neroswap-aggregator`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.7 },
{ url: `${base}/blog/crypto-checkout-infrastructure`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.7 },
{ url: `${base}/blog/bauhaus-echo-launch`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.6 },
{ url: `${base}/blog/smirk-wallet-release`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.6 },
{ url: `${base}/blog/website-relaunch-2026`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.5 },
{ url: `${base}/blog/wownero-moon-launch-release`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.6 },
];
}
+6
View File
@@ -47,6 +47,7 @@ export function getPost(slug: string): Post | null {
const raw = fs.readFileSync(filePath, "utf-8");
const { data, content } = matter(raw);
if (data.draft === true) return null;
const iso = data.date ?? "";
return {
slug,
@@ -65,5 +66,10 @@ export function getAllSlugs(): string[] {
return fs
.readdirSync(CONTENT_DIR)
.filter((f) => f.endsWith(".mdx"))
.filter((f) => {
const raw = fs.readFileSync(path.join(CONTENT_DIR, f), "utf-8");
const { data } = matter(raw);
return data.draft !== true;
})
.map((f) => f.replace(/\.mdx$/, ""));
}