diff --git a/apps/web/public/images/og/bauhaus-echo.png b/apps/web/public/images/og/bauhaus-echo.png
new file mode 100644
index 0000000..5259c89
Binary files /dev/null and b/apps/web/public/images/og/bauhaus-echo.png differ
diff --git a/apps/web/public/images/og/custom-websites.png b/apps/web/public/images/og/custom-websites.png
new file mode 100644
index 0000000..a46aa7c
Binary files /dev/null and b/apps/web/public/images/og/custom-websites.png differ
diff --git a/apps/web/public/images/og/neroswap.png b/apps/web/public/images/og/neroswap.png
new file mode 100644
index 0000000..c074b25
Binary files /dev/null and b/apps/web/public/images/og/neroswap.png differ
diff --git a/apps/web/public/images/og/smirk-wallet.png b/apps/web/public/images/og/smirk-wallet.png
new file mode 100644
index 0000000..44bb927
Binary files /dev/null and b/apps/web/public/images/og/smirk-wallet.png differ
diff --git a/apps/web/public/images/og/such-moon-launch.png b/apps/web/public/images/og/such-moon-launch.png
new file mode 100644
index 0000000..61fa53c
Binary files /dev/null and b/apps/web/public/images/og/such-moon-launch.png differ
diff --git a/apps/web/public/images/og/suchoice.png b/apps/web/public/images/og/suchoice.png
new file mode 100644
index 0000000..c52cc56
Binary files /dev/null and b/apps/web/public/images/og/suchoice.png differ
diff --git a/apps/web/public/images/og/vegan-iq.png b/apps/web/public/images/og/vegan-iq.png
new file mode 100644
index 0000000..36a0799
Binary files /dev/null and b/apps/web/public/images/og/vegan-iq.png differ
diff --git a/apps/web/public/images/og/webshops.png b/apps/web/public/images/og/webshops.png
new file mode 100644
index 0000000..2c0cc06
Binary files /dev/null and b/apps/web/public/images/og/webshops.png differ
diff --git a/apps/web/public/images/og/wownerogue.png b/apps/web/public/images/og/wownerogue.png
new file mode 100644
index 0000000..49b11ba
Binary files /dev/null and b/apps/web/public/images/og/wownerogue.png differ
diff --git a/apps/web/src/app/products/bauhaus-echo/page.tsx b/apps/web/src/app/products/bauhaus-echo/page.tsx
index 16bab1c..7505fd1 100644
--- a/apps/web/src/app/products/bauhaus-echo/page.tsx
+++ b/apps/web/src/app/products/bauhaus-echo/page.tsx
@@ -15,7 +15,11 @@ export const metadata: Metadata = {
title: "Bauhaus Echo: Visual Memory Puzzle Game | Such Software",
description: "A Bauhaus-inspired memory puzzle game with 5 modes and a pressure-free Zen sandbox. Play free on iOS, Android, web, and desktop.",
type: "website",
- images: [{ url: "/images/products/bauhaus-echo.png", width: 512, height: 512, alt: "Bauhaus Echo" }],
+ images: ["/images/og/bauhaus-echo.png"],
+ },
+ twitter: {
+ card: "summary_large_image",
+ images: ["/images/og/bauhaus-echo.png"],
},
};
diff --git a/apps/web/src/app/products/custom-websites/page.tsx b/apps/web/src/app/products/custom-websites/page.tsx
index 90a2836..65a233c 100644
--- a/apps/web/src/app/products/custom-websites/page.tsx
+++ b/apps/web/src/app/products/custom-websites/page.tsx
@@ -14,6 +14,11 @@ export const metadata: Metadata = {
title: "Custom Web Apps & Real-Time Platforms | Such Software",
description: "Fast, accessible real-time web apps: multiplayer games, live dashboards, and crypto platforms with WebSocket sync. Next.js, Socket.io, WebRTC.",
type: "website",
+ images: ["/images/og/custom-websites.png"],
+ },
+ twitter: {
+ card: "summary_large_image",
+ images: ["/images/og/custom-websites.png"],
},
};
diff --git a/apps/web/src/app/products/neroswap/page.tsx b/apps/web/src/app/products/neroswap/page.tsx
index 061ee21..97275e6 100644
--- a/apps/web/src/app/products/neroswap/page.tsx
+++ b/apps/web/src/app/products/neroswap/page.tsx
@@ -3,7 +3,7 @@ import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button";
import Link from "next/link";
-import { JsonLd, breadcrumbLd, softwareAppLd } from "@/components/seo/json-ld";
+import { JsonLd, breadcrumbLd, softwareAppLd, faqLd } from "@/components/seo/json-ld";
import type { Metadata } from "next";
export const metadata: Metadata = {
@@ -17,6 +17,11 @@ export const metadata: Metadata = {
"Compare live Monero, Wownero, and Bitcoin prices across 6 DEX and no-KYC CEX orderbooks in real time. No accounts, no custody, no tracking.",
type: "website",
url: "https://such.software/products/neroswap",
+ images: ["/images/og/neroswap.png"],
+ },
+ twitter: {
+ card: "summary_large_image",
+ images: ["/images/og/neroswap.png"],
},
keywords: [
"neroswap",
@@ -33,6 +38,13 @@ export const metadata: Metadata = {
],
};
+const faqs = [
+ { q: "Is Neroswap KYC-free?", a: "Yes. Neroswap aggregates public orderbook data and never asks for an account or identity. Every exchange it lists is either fully decentralized or no-KYC." },
+ { q: "Does Neroswap hold my coins?", a: "No. Neroswap is a read-only price and orderbook aggregator, not an exchange or custodian. You trade on whichever source venue you choose; Neroswap never touches funds." },
+ { q: "What does Neroswap actually do?", a: "It pulls live prices from six DEX and no-KYC CEX venues and compares Monero, Wownero, and Bitcoin markets in real time, then links each row straight to the source market so you can trade at the best price." },
+ { q: "Which coins are supported?", a: "Monero (XMR), Wownero (WOW), and Bitcoin (BTC) are the headline markets, alongside other pairs offered by the supported exchanges." },
+];
+
const exchanges = [
{
name: "BasicSwap",
@@ -110,6 +122,7 @@ export default function NeroswapPage() {
price: "0",
offerUrl: "https://neroswap.com",
})} />
+
+
{f.a}
+
Related:{" "}
Smirk Wallet,{" "}
Wownerogue, and our{" "}
diff --git a/apps/web/src/app/products/smirk-wallet/page.tsx b/apps/web/src/app/products/smirk-wallet/page.tsx
index a8d092b..8a663ad 100644
--- a/apps/web/src/app/products/smirk-wallet/page.tsx
+++ b/apps/web/src/app/products/smirk-wallet/page.tsx
@@ -14,6 +14,11 @@ export const metadata: Metadata = {
title: "Smirk Wallet: Non-Custodial Crypto Tipping by Username",
description: "Tip BTC, LTC, XMR, WOW, and GRIN on Telegram or Discord by username. A non-custodial browser wallet: your keys never leave your device.",
type: "website",
+ images: ["/images/og/smirk-wallet.png"],
+ },
+ twitter: {
+ card: "summary_large_image",
+ images: ["/images/og/smirk-wallet.png"],
},
};
diff --git a/apps/web/src/app/products/such-moon-launch/page.tsx b/apps/web/src/app/products/such-moon-launch/page.tsx
index 9cc98a0..40c0347 100644
--- a/apps/web/src/app/products/such-moon-launch/page.tsx
+++ b/apps/web/src/app/products/such-moon-launch/page.tsx
@@ -14,6 +14,11 @@ export const metadata: Metadata = {
title: "Such Moon Launch: Pixel Rocket Arcade Game | Such Software",
description: "Fly a rocket across the solar system in this Wownero-themed pixel arcade. 11 levels, 4 weapons, tilt-to-steer, endless mode. Free on iOS, Android, web, desktop.",
type: "website",
+ images: ["/images/og/such-moon-launch.png"],
+ },
+ twitter: {
+ card: "summary_large_image",
+ images: ["/images/og/such-moon-launch.png"],
},
};
diff --git a/apps/web/src/app/products/suchoice/page.tsx b/apps/web/src/app/products/suchoice/page.tsx
index 700ab87..617b419 100644
--- a/apps/web/src/app/products/suchoice/page.tsx
+++ b/apps/web/src/app/products/suchoice/page.tsx
@@ -15,7 +15,11 @@ export const metadata: Metadata = {
title: "Suchoice: Spin the Wheel & AI Decision Maker App | Such Software",
description: "Can't decide? Snap a photo and let AI read your options, spin the wheel, flip a coin, or roll 3D dice. The fun decision-maker app for iOS and Android.",
type: "website",
- images: [{ url: "/images/products/suchoice.png", width: 512, height: 512, alt: "Suchoice" }],
+ images: ["/images/og/suchoice.png"],
+ },
+ twitter: {
+ card: "summary_large_image",
+ images: ["/images/og/suchoice.png"],
},
};
diff --git a/apps/web/src/app/products/vegan-iq/page.tsx b/apps/web/src/app/products/vegan-iq/page.tsx
index 8b30dfa..23bf87c 100644
--- a/apps/web/src/app/products/vegan-iq/page.tsx
+++ b/apps/web/src/app/products/vegan-iq/page.tsx
@@ -15,7 +15,11 @@ export const metadata: Metadata = {
title: "Vegan IQ: Plant-Based Trivia Game | Such Software",
description: "Test your plant-based knowledge with 1,000+ trivia questions across 8 categories, plus a daily challenge and weekly quiz. Free vegan trivia for iOS and Android.",
type: "website",
- images: [{ url: "/images/products/vegan-iq.png", width: 512, height: 512, alt: "Vegan IQ" }],
+ images: ["/images/og/vegan-iq.png"],
+ },
+ twitter: {
+ card: "summary_large_image",
+ images: ["/images/og/vegan-iq.png"],
},
};
diff --git a/apps/web/src/app/products/webshops/page.tsx b/apps/web/src/app/products/webshops/page.tsx
index eaba277..7d16ded 100644
--- a/apps/web/src/app/products/webshops/page.tsx
+++ b/apps/web/src/app/products/webshops/page.tsx
@@ -2,7 +2,7 @@ import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button";
-import { JsonLd, breadcrumbLd } from "@/components/seo/json-ld";
+import { JsonLd, breadcrumbLd, faqLd } from "@/components/seo/json-ld";
import Link from "next/link";
import type { Metadata } from "next";
@@ -14,9 +14,22 @@ export const metadata: Metadata = {
title: "Custom Online Stores & E-Commerce Development | Such Software",
description: "Custom online stores with no upfront cost and revenue-share pricing. Own your data and escape template limits. Built on Medusa.",
type: "website",
+ images: ["/images/og/webshops.png"],
+ },
+ twitter: {
+ card: "summary_large_image",
+ images: ["/images/og/webshops.png"],
},
};
+const faqs = [
+ { q: "How does revenue-share pricing work?", a: "Instead of a large upfront build fee, we build and host your store at our own cost and take a small percentage of sales once you are live. We only make money when you do." },
+ { q: "What is headless commerce, and why Medusa?", a: "Headless means the storefront is decoupled from the commerce backend. We use Medusa v2 (open-source, Node.js) for the backend and Next.js for a fast custom storefront, so you are not boxed in by a template or a hosted platform's limits." },
+ { q: "Do I own my data?", a: "Yes. Unlike hosted platforms, you own your customer data, product catalog, and order history outright. There is no platform lock-in." },
+ { q: "Can you migrate my existing store?", a: "Yes. We can migrate catalogs, customers, and order history from Shopify, WooCommerce, and other platforms." },
+ { q: "Which payment methods are supported?", a: "Stripe and PayPal out of the box, with optional cryptocurrency payments if you want them." },
+];
+
export default function WebshopsPage() {
return (
{f.a} {f.a}
Want the build notes? Read{" "}
diff --git a/apps/web/src/app/services/page.tsx b/apps/web/src/app/services/page.tsx
index ed732f3..59a5195 100644
--- a/apps/web/src/app/services/page.tsx
+++ b/apps/web/src/app/services/page.tsx
@@ -6,8 +6,17 @@ import { ScrollReveal } from "@/components/scroll-reveal";
import Image from "next/image";
import Link from "next/link";
import { Card } from "@repo/ui/components/card";
+import { JsonLd, faqLd } from "@/components/seo/json-ld";
import type { Metadata } from "next";
+const faqs = [
+ { q: "What does Such Software build?", a: "Custom e-commerce on Medusa v2, real-time web apps and multiplayer experiences, and cryptocurrency and Monero integrations and consulting. We work across the stack, from backend infrastructure to accessible front-ends." },
+ { q: "Where are you based?", a: "We are a software studio in Kennett Square, Pennsylvania, working with clients remotely across the US and beyond." },
+ { q: "Do you build for accessibility?", a: "Yes. We build to WCAG 2.2 and audit for accessibility and Core Web Vitals as part of every project, not as an afterthought." },
+ { q: "How does a project start?", a: "Reach out through the contact form with what you need. We scope it, propose an approach, and from there it can run as a fixed project or a performance-based revenue-share partnership." },
+ { q: "Do you take revenue-share work?", a: "Yes. For a limited number of partners we build and operate products (such as webshops) for a share of revenue instead of a large upfront fee, so our incentives line up with yours." },
+];
+
export const metadata: Metadata = {
title: "Medusa, Web App & Crypto Development Services | Such Software",
description: "Custom e-commerce on Medusa v2, real-time web apps, and Monero/crypto consulting. A Kennett Square, PA studio building for scale, accessibility, and performance.",
@@ -24,6 +33,7 @@ export const metadata: Metadata = {
export default function ServicesPage() {
return (
{f.a}FAQ
+ {f.q}
+ FAQ
+ {f.q}
+ Frequently Asked Questions
+ {f.q}
+ Let's build together
diff --git a/apps/web/src/app/sitemap.ts b/apps/web/src/app/sitemap.ts
index b0b9ba2..be34a5f 100644
--- a/apps/web/src/app/sitemap.ts
+++ b/apps/web/src/app/sitemap.ts
@@ -4,30 +4,36 @@ import { getAllPosts } from "@/lib/blog";
export default function sitemap(): MetadataRoute.Sitemap {
const base = "https://such.software";
+ // Stable content-update date for the static pages. Using `new Date()` here would stamp
+ // every page as "modified now" on every build, which is noise that dilutes the freshness
+ // signal. Bump this when the site's static content meaningfully changes. (Blog posts carry
+ // their own per-post dates below.)
+ const updated = new Date("2026-06-01");
+
const staticPages: MetadataRoute.Sitemap = [
- { url: base, lastModified: new Date(), changeFrequency: "weekly", priority: 1.0 },
- { url: `${base}/products`, lastModified: new Date(), changeFrequency: "weekly", priority: 0.9 },
- { url: `${base}/products/wownerogue`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.8 },
- { 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/vegan-iq`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.8 },
- { url: `${base}/products/suchoice`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.7 },
- { url: `${base}/products/such-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}/privacy`, lastModified: new Date(), changeFrequency: "yearly", priority: 0.3 },
- { url: `${base}/terms`, lastModified: new Date(), changeFrequency: "yearly", priority: 0.3 },
- { url: `${base}/apps`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.5 },
- { url: `${base}/blog`, lastModified: new Date(), changeFrequency: "weekly", priority: 0.8 },
+ { url: base, lastModified: updated, changeFrequency: "weekly", priority: 1.0 },
+ { url: `${base}/products`, lastModified: updated, changeFrequency: "weekly", priority: 0.9 },
+ { url: `${base}/products/wownerogue`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
+ { url: `${base}/products/neroswap`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
+ { url: `${base}/products/webshops`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
+ { url: `${base}/products/custom-websites`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
+ { url: `${base}/products/smirk-wallet`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
+ { url: `${base}/products/bauhaus-echo`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
+ { url: `${base}/products/vegan-iq`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
+ { url: `${base}/products/suchoice`, lastModified: updated, changeFrequency: "monthly", priority: 0.7 },
+ { url: `${base}/products/such-moon-launch`, lastModified: updated, changeFrequency: "monthly", priority: 0.7 },
+ { url: `${base}/services`, lastModified: updated, changeFrequency: "monthly", priority: 0.9 },
+ { url: `${base}/about`, lastModified: updated, changeFrequency: "monthly", priority: 0.7 },
+ { url: `${base}/support`, lastModified: updated, changeFrequency: "monthly", priority: 0.5 },
+ { url: `${base}/privacy`, lastModified: updated, changeFrequency: "yearly", priority: 0.3 },
+ { url: `${base}/terms`, lastModified: updated, changeFrequency: "yearly", priority: 0.3 },
+ { url: `${base}/apps`, lastModified: updated, changeFrequency: "monthly", priority: 0.5 },
+ { url: `${base}/blog`, lastModified: updated, changeFrequency: "weekly", priority: 0.8 },
];
const blogPosts: MetadataRoute.Sitemap = getAllPosts().map((post) => ({
url: `${base}/blog/${post.slug}`,
- lastModified: post.date ? new Date(post.date + "T12:00:00") : new Date(),
+ lastModified: post.date ? new Date(post.date + "T12:00:00") : updated,
changeFrequency: "monthly",
priority: 0.6,
}));
diff --git a/apps/web/src/components/seo/json-ld.tsx b/apps/web/src/components/seo/json-ld.tsx
index 1d799cd..42c43cc 100644
--- a/apps/web/src/components/seo/json-ld.tsx
+++ b/apps/web/src/components/seo/json-ld.tsx
@@ -70,6 +70,19 @@ export function softwareAppLd(opts: SoftwareAppOpts) {
return data;
}
+/** FAQPage node. Pair it with the SAME questions/answers rendered visibly on the page. */
+export function faqLd(items: { q: string; a: string }[]) {
+ return {
+ "@context": "https://schema.org",
+ "@type": "FAQPage",
+ mainEntity: items.map((it) => ({
+ "@type": "Question",
+ name: it.q,
+ acceptedAnswer: { "@type": "Answer", text: it.a },
+ })),
+ };
+}
+
/** BlogPosting node for an article page. */
export function articleLd(opts: { title: string; description: string; slug: string; date: string }) {
const url = `${SITE}/blog/${opts.slug}`;