Add GresIQ alternative pages for Supabase, Neon, and PlanetScale

Add comparison pages for GresIQ as an alternative to Supabase, Neon, and PlanetScale. Each page includes a feature comparison table, fit criteria, FAQ, and structured data for SEO. Also add a WordCycler component for animated word cycling on the homepage. Update homepage copy for clarity and brevity. Remove unused component file.

keypair34 committed May 9, 2026 at 00:25 UTC 69e8c525ca87cd6a259c5dab155330c25a66c62f
8 files changed +1410 -117
app/neon-alternative/page.tsx new
+361
@@ -0,0 +1,361 @@
1 +import type { Metadata } from "next";
2 +import Link from "next/link";
3 +import { StructuredData } from "@/components/structured-data";
4 +import { absoluteUrl, genPageMetadata } from "../seo";
5 +
6 +const comparisonRows = [
7 + {
8 + label: "What it is",
9 + smbcloud:
10 + "A data layer service managed inside your smbCloud project alongside Deploy, Auth, and Mail.",
11 + neon: "A serverless PostgreSQL platform with instant branching, autoscaling, and a generous free tier.",
12 + },
13 + {
14 + label: "Best fit",
15 + smbcloud:
16 + "Teams that want always-on database management without cold starts or serverless compute trade-offs.",
17 + neon: "Teams building on serverless or edge infrastructure that want Postgres to scale down to zero.",
18 + },
19 + {
20 + label: "Cold starts",
21 + smbcloud:
22 + "No serverless compute model. Database connections behave like a standard managed database.",
23 + neon: "Serverless by design. Databases can pause and wake on first connection, which may add latency.",
24 + },
25 + {
26 + label: "Branching",
27 + smbcloud:
28 + "Standard database management. Use your framework migration tooling the same way you always have.",
29 + neon: "Instant Postgres branching — a core feature for preview environments and development workflows.",
30 + },
31 + {
32 + label: "Why teams pick it",
33 + smbcloud:
34 + "Because they want a predictable always-on data layer inside one coherent platform.",
35 + neon: "Because they want Postgres that scales to zero on the free tier and integrates with serverless workflows.",
36 + },
37 +];
38 +
39 +const smbcloudFits = [
40 + "You want an always-on database without cold start concerns.",
41 + "You are already using smbCloud Deploy or Auth and want the data layer in the same place.",
42 + "You do not need Postgres branching for preview environments or CI workflows.",
43 + "You prefer a predictable operational model over serverless autoscaling.",
44 +];
45 +
46 +const neonStillFits = [
47 + "Your architecture is serverless or edge-first and you want Postgres that matches it.",
48 + "You rely on Neon's branching for preview environments or per-PR databases.",
49 + "The free tier's scale-to-zero behavior saves you real money.",
50 + "You are deeply integrated with Neon's serverless driver or platform integrations.",
51 +];
52 +
53 +const faqs = [
54 + {
55 + question: "Is smbCloud GresIQ a Neon replacement?",
56 + answer:
57 + "For teams running traditional server-side apps that want an always-on managed database, it can be. If you never leaned on Neon's serverless scaling or branching features, GresIQ is worth evaluating.",
58 + },
59 + {
60 + question: "Does GresIQ support PostgreSQL?",
61 + answer:
62 + "GresIQ is built for relational database management and is designed to work alongside the smbCloud stack. It is not built around the serverless Postgres model the way Neon is.",
63 + },
64 + {
65 + question: "Will I notice cold starts with GresIQ?",
66 + answer:
67 + "No. GresIQ does not use a serverless compute model. Your database stays on and connections behave the same way they would with any managed relational database.",
68 + },
69 + {
70 + question: "Who should consider switching from Neon to GresIQ?",
71 + answer:
72 + "Teams using Neon as a hosted Postgres instance that do not rely on branching or scale-to-zero and would prefer to manage less tooling across fewer platforms.",
73 + },
74 +];
75 +
76 +export const metadata: Metadata = {
77 + ...genPageMetadata({
78 + title: "Neon alternative for teams that want always-on managed Postgres",
79 + description:
80 + "Looking for a Neon alternative? smbCloud GresIQ is an always-on data layer inside the same platform as your app, auth, and mail — no cold starts, no serverless trade-offs.",
81 + path: "/neon-alternative",
82 + keywords: [
83 + "Neon alternative",
84 + "Neon Postgres alternative",
85 + "serverless Postgres alternative",
86 + "always-on managed database",
87 + "GresIQ database",
88 + "smbCloud GresIQ",
89 + "managed PostgreSQL",
90 + ],
91 + }),
92 +};
93 +
94 +const structuredData = {
95 + "@context": "https://schema.org",
96 + "@graph": [
97 + {
98 + "@type": "WebPage",
99 + "@id": absoluteUrl("/neon-alternative#webpage"),
100 + url: absoluteUrl("/neon-alternative"),
101 + name: "Neon alternative for teams that want always-on managed Postgres",
102 + description:
103 + "Compare smbCloud GresIQ and Neon for teams that want a standard always-on database instead of serverless Postgres.",
104 + isPartOf: {
105 + "@id": absoluteUrl("/#website"),
106 + },
107 + },
108 + {
109 + "@type": "BreadcrumbList",
110 + itemListElement: [
111 + {
112 + "@type": "ListItem",
113 + position: 1,
114 + name: "Home",
115 + item: absoluteUrl("/"),
116 + },
117 + {
118 + "@type": "ListItem",
119 + position: 2,
120 + name: "Neon alternative",
121 + item: absoluteUrl("/neon-alternative"),
122 + },
123 + ],
124 + },
125 + {
126 + "@type": "FAQPage",
127 + mainEntity: faqs.map((faq) => ({
128 + "@type": "Question",
129 + name: faq.question,
130 + acceptedAnswer: {
131 + "@type": "Answer",
132 + text: faq.answer,
133 + },
134 + })),
135 + },
136 + ],
137 +};
138 +
139 +export default function NeonAlternativePage() {
140 + return (
141 + <div>
142 + <StructuredData data={structuredData} />
143 +
144 + <section className="relative overflow-hidden px-6 pt-24 pb-16 sm:pt-32 sm:pb-20 lg:px-8">
145 + <div className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[520px] bg-[radial-gradient(ellipse_75%_50%_at_50%_0%,rgba(99,102,241,0.18),transparent)] dark:bg-[radial-gradient(ellipse_75%_50%_at_50%_0%,rgba(99,102,241,0.12),transparent)]" />
146 +
147 + <div className="mx-auto max-w-4xl">
148 + <div className="mb-6 inline-flex items-center gap-2 rounded-full border border-indigo-200 bg-indigo-50 px-4 py-1.5 dark:border-indigo-500/30 dark:bg-indigo-500/10">
149 + <span className="h-1.5 w-1.5 rounded-full bg-indigo-500" />
150 + <span className="text-xs font-semibold uppercase tracking-widest text-indigo-700 dark:text-indigo-400">
151 + GresIQ comparison
152 + </span>
153 + </div>
154 +
155 + <h1 className="max-w-3xl text-4xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-6xl leading-[1.05]">
156 + Looking for a Neon alternative?
157 + </h1>
158 +
159 + <p className="mt-6 max-w-3xl text-lg leading-8 text-slate-600 dark:text-slate-400">
160 + Neon built something clever with serverless Postgres. But
161 + scale-to-zero and cold starts are the wrong trade-off for most
162 + server-side apps. smbCloud GresIQ gives you an always-on data layer
163 + inside the same platform as your app, auth, and mail — with no
164 + serverless surprises.
165 + </p>
166 +
167 + <div className="mt-8 flex flex-col gap-4 sm:flex-row">
168 + <Link
169 + href="https://console.smbcloud.xyz"
170 + target="_blank"
171 + rel="noopener noreferrer"
172 + className="inline-flex items-center justify-center rounded-lg bg-indigo-600 px-8 py-3.5 text-sm font-semibold text-white shadow-lg shadow-indigo-500/20 transition-all hover:bg-indigo-500 hover:shadow-indigo-500/30 active:scale-[0.98]"
173 + >
174 + Try smbCloud GresIQ
175 + </Link>
176 + <Link
177 + href="/services"
178 + className="inline-flex items-center justify-center rounded-lg border border-slate-300 px-8 py-3.5 text-sm font-semibold text-slate-700 transition-colors hover:bg-slate-50 dark:border-white/[0.12] dark:text-slate-200 dark:hover:bg-white/[0.04]"
179 + >
180 + See all services
181 + </Link>
182 + </div>
183 + </div>
184 + </section>
185 +
186 + <section className="px-6 pb-24 lg:px-8">
187 + <div className="mx-auto max-w-5xl grid gap-8 lg:grid-cols-[0.95fr_1.05fr]">
188 + <div>
189 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
190 + Why teams look beyond Neon
191 + </p>
192 + <h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
193 + Usually they wanted managed Postgres. The serverless model came
194 + with it.
195 + </h2>
196 + <p className="mt-5 text-base leading-8 text-slate-600 dark:text-slate-400">
197 + Neon is a genuine innovation in the Postgres space. But for Rails,
198 + Node.js, and Vapor apps running on standard servers, an always-on
199 + database is simpler to reason about and easier to operate. GresIQ
200 + keeps the database predictable and keeps it close to the rest of
201 + your stack.
202 + </p>
203 + </div>
204 +
205 + <div className="rounded-2xl border border-slate-200 bg-slate-50 p-8 dark:border-white/[0.06] dark:bg-white/[0.02]">
206 + <p className="text-sm font-semibold text-slate-900 dark:text-white">
207 + What GresIQ does well
208 + </p>
209 + <ul className="mt-5 space-y-4 text-sm leading-7 text-slate-600 dark:text-slate-400">
210 + <li className="flex gap-3">
211 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-500" />
212 + <span>Always-on relational database management — no cold starts, no wake-up latency.</span>
213 + </li>
214 + <li className="flex gap-3">
215 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-500" />
216 + <span>Lives inside the same platform as your app, auth, and mail.</span>
217 + </li>
218 + <li className="flex gap-3">
219 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-500" />
220 + <span>Standard connection model. Works with the ORM and migration tooling you already use.</span>
221 + </li>
222 + </ul>
223 + </div>
224 + </div>
225 + </section>
226 +
227 + <section className="border-t border-slate-200 px-6 py-24 dark:border-white/[0.06] lg:px-8">
228 + <div className="mx-auto max-w-5xl">
229 + <div className="mb-12 max-w-3xl">
230 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
231 + smbCloud GresIQ vs Neon
232 + </p>
233 + <h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
234 + Here is the trade, without the usual sales fog.
235 + </h2>
236 + </div>
237 +
238 + <div className="overflow-hidden rounded-2xl border border-slate-200 dark:border-white/[0.06]">
239 + <div className="grid grid-cols-[0.8fr_1fr_1fr] bg-slate-50 text-sm font-semibold text-slate-900 dark:bg-white/[0.03] dark:text-white">
240 + <div className="border-b border-slate-200 px-5 py-4 dark:border-white/[0.06]">
241 + Area
242 + </div>
243 + <div className="border-b border-l border-slate-200 px-5 py-4 dark:border-white/[0.06]">
244 + smbCloud GresIQ
245 + </div>
246 + <div className="border-b border-l border-slate-200 px-5 py-4 dark:border-white/[0.06]">
247 + Neon
248 + </div>
249 + </div>
250 + {comparisonRows.map((row) => (
251 + <div
252 + key={row.label}
253 + className="grid grid-cols-[0.8fr_1fr_1fr] text-sm leading-7 text-slate-600 dark:text-slate-400"
254 + >
255 + <div className="border-b border-slate-200 bg-white px-5 py-5 font-semibold text-slate-900 dark:border-white/[0.06] dark:bg-white/[0.02] dark:text-white">
256 + {row.label}
257 + </div>
258 + <div className="border-b border-l border-slate-200 bg-white px-5 py-5 dark:border-white/[0.06] dark:bg-white/[0.02]">
259 + {row.smbcloud}
260 + </div>
261 + <div className="border-b border-l border-slate-200 bg-white px-5 py-5 dark:border-white/[0.06] dark:bg-white/[0.02]">
262 + {row.neon}
263 + </div>
264 + </div>
265 + ))}
266 + </div>
267 + </div>
268 + </section>
269 +
270 + <section className="px-6 py-24 lg:px-8">
271 + <div className="mx-auto grid max-w-5xl gap-6 lg:grid-cols-2">
272 + <div className="rounded-2xl border border-slate-200 bg-white p-8 dark:border-white/[0.06] dark:bg-white/[0.02]">
273 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
274 + Pick smbCloud GresIQ if
275 + </p>
276 + <ul className="mt-6 space-y-4 text-sm leading-7 text-slate-600 dark:text-slate-400">
277 + {smbcloudFits.map((item) => (
278 + <li key={item} className="flex gap-3">
279 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-500" />
280 + <span>{item}</span>
281 + </li>
282 + ))}
283 + </ul>
284 + </div>
285 +
286 + <div className="rounded-2xl border border-slate-200 bg-slate-50 p-8 dark:border-white/[0.06] dark:bg-white/[0.02]">
287 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
288 + Stay on Neon if
289 + </p>
290 + <ul className="mt-6 space-y-4 text-sm leading-7 text-slate-600 dark:text-slate-400">
291 + {neonStillFits.map((item) => (
292 + <li key={item} className="flex gap-3">
293 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-slate-400" />
294 + <span>{item}</span>
295 + </li>
296 + ))}
297 + </ul>
298 + </div>
299 + </div>
300 + </section>
301 +
302 + <section className="px-6 pb-24 lg:px-8">
303 + <div className="mx-auto max-w-3xl">
304 + <div className="mb-10">
305 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
306 + FAQ
307 + </p>
308 + <h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
309 + Common questions
310 + </h2>
311 + </div>
312 + <div className="space-y-4">
313 + {faqs.map((faq) => (
314 + <div
315 + key={faq.question}
316 + className="rounded-2xl border border-slate-200 bg-white p-6 dark:border-white/[0.06] dark:bg-white/[0.02]"
317 + >
318 + <h3 className="text-lg font-semibold text-slate-900 dark:text-white">
319 + {faq.question}
320 + </h3>
321 + <p className="mt-3 text-sm leading-7 text-slate-600 dark:text-slate-400">
322 + {faq.answer}
323 + </p>
324 + </div>
325 + ))}
326 + </div>
327 + </div>
328 + </section>
329 +
330 + <section className="px-6 pb-20 lg:px-8">
331 + <div className="mx-auto max-w-3xl">
332 + <div className="relative overflow-hidden rounded-2xl bg-indigo-600 px-8 py-14 text-center shadow-2xl shadow-indigo-500/20">
333 + <div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(255,255,255,0.1),transparent)]" />
334 + <h2 className="relative text-3xl font-bold tracking-tight text-white">
335 + If Neon feels like the wrong model for your stack, try GresIQ.
336 + </h2>
337 + <p className="relative mt-4 text-indigo-200">
338 + Run it on one project. That is usually enough to know.
339 + </p>
340 + <div className="relative mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
341 + <Link
342 + href="https://console.smbcloud.xyz"
343 + target="_blank"
344 + rel="noopener noreferrer"
345 + className="inline-flex rounded-lg bg-white px-8 py-3.5 text-sm font-semibold text-indigo-700 shadow-sm transition-colors hover:bg-indigo-50"
346 + >
347 + Start with smbCloud
348 + </Link>
349 + <Link
350 + href="/supabase-alternative"
351 + className="inline-flex rounded-lg border border-white/20 px-8 py-3.5 text-sm font-semibold text-white transition-colors hover:bg-white/10"
352 + >
353 + Compare Supabase alternative
354 + </Link>
355 + </div>
356 + </div>
357 + </div>
358 + </section>
359 + </div>
360 + );
361 +}
app/page.tsx
+126 -116
@@ -6,50 +6,50 @@ import { FaApple, FaGooglePlay, FaWindows } from "react-icons/fa";
6 import { StructuredData } from "@/components/structured-data";
7 import siteMetadata from "@/content/siteMetadata";
8 import { absoluteUrl, genPageMetadata } from "./seo";
9 +import {
10 + ServiceWordCycler,
11 + FrameworkWordCycler,
12 +} from "@/components/word-cycler";
13
14 const proofPoints = [
15 {
16 title: "One command",
13 - description: "Deploy from your project with the `smb` CLI.",
17 + description: "Run `smb` to deploy.",
18 },
19 {
20 title: "SSH-first",
17 - description: "Keep the deploy flow closer to how developers already work.",
21 + description: "No extra layer between you and the server.",
22 },
23 {
24 title: "Live logs",
21 - description:
22 - "Watch deploy progress, app checks, and runtime feedback in real time.",
25 + description: "Watch the deploy as it happens.",
26 },
27 {
28 title: "Fast rollbacks",
26 - description: "Go back quickly when a release needs to be reversed.",
29 + description: "One command back when something breaks.",
30 },
31 ];
32
33 const reasonsToChoose = [
34 {
35 title: "No platform maze",
33 - description:
34 - "We built smbCloud so deploys would feel boring again. Run `smb`, watch the release, move on.",
36 + description: "Run `smb`, watch the release, move on.",
37 },
38 {
39 title: "Built for real stacks",
38 - description:
39 - "Node.js, Rails, and Vapor are the stacks we actually care about. This product is shaped around them on purpose.",
40 + description: "Node.js, Rails, and Vapor. That's it.",
41 },
42 {
43 title: "Closer to normal ops",
43 - description:
44 - "If your team already likes SSH and straightforward deploys, smbCloud will make sense fast.",
44 + description: "SSH in, deploy out. Nothing new to learn.",
45 },
46 ];
47
48 const bestFit = [
49 - "Small product teams shipping web apps and APIs",
50 - "Agencies and consultants managing client deployments",
51 - "Founders who miss when deploys felt simple",
52 - "Teams that already like SSH and fewer moving parts",
49 + "Small teams shipping web apps and APIs",
50 + "Agencies managing client deployments",
51 + "Founders who want deploys to be boring",
52 + "Teams that like SSH and fewer moving parts",
53 ];
54
55 const deployComparisonPages = [
@@ -57,8 +57,7 @@ const deployComparisonPages = [
57 href: "/heroku-alternative",
58 eyebrow: "Deploy comparison",
59 title: "Heroku alternative",
60 - description:
61 - "If you still want simplicity, but with less platform in the middle.",
60 + description: "Simpler deploys, less platform in the middle.",
61 bullets: [
62 "Why some Rails, Node.js, and Vapor teams leave Heroku",
63 "Where smbCloud fits, and where it does not",
@@ -68,8 +67,7 @@ const deployComparisonPages = [
67 href: "/railway-alternative",
68 eyebrow: "Deploy comparison",
69 title: "Railway alternative",
71 - description:
72 - "If Railway feels a bit too broad and you want a calmer deploy flow.",
70 + description: "A calmer deploy flow for teams that want less surface area.",
71 bullets: [
72 "What you gain from a narrower platform",
73 "Why some teams prefer less surface area",
@@ -82,8 +80,7 @@ const authComparisonPages = [
80 href: "/auth0-alternative",
81 eyebrow: "Auth comparison",
82 title: "Auth0 alternative",
85 - description:
86 - "If you want hosted auth flows without turning identity into its own platform project.",
83 + description: "Hosted auth without the identity platform overhead.",
84 bullets: [
85 "Compare hosted sign-in, signup, and session flows against a bigger identity stack",
86 "See where smbCloud Auth fits for smaller teams that want less auth sprawl",
@@ -93,7 +90,7 @@ const authComparisonPages = [
90 href: "/clerk-alternative",
91 eyebrow: "Auth comparison",
92 title: "Clerk alternative",
96 - description: "If you want auth to stay simple, hosted, and out of the way.",
93 + description: "Auth that stays out of the way.",
94 bullets: [
95 "Compare developer-friendly hosted auth against a calmer auth workflow",
96 "See when smbCloud Auth is a better fit and when Clerk still makes more sense",
@@ -103,8 +100,7 @@ const authComparisonPages = [
100 href: "/supabase-auth-alternative",
101 eyebrow: "Auth comparison",
102 title: "Supabase Auth alternative",
106 - description:
107 - "If you want hosted auth without adopting a bigger backend platform than you need.",
103 + description: "Just auth, without adopting a full backend platform.",
104 bullets: [
105 "Compare focused auth flows against auth bundled inside a larger platform",
106 "See when smbCloud Auth is the cleaner fit for accounts and sessions",
@@ -112,13 +108,47 @@ const authComparisonPages = [
108 },
109 ];
110
111 +const gresiqComparisonPages = [
112 + {
113 + href: "/supabase-alternative",
114 + eyebrow: "GresIQ comparison",
115 + title: "Supabase alternative",
116 + description: "A data layer that doesn't become its own platform.",
117 + bullets: [
118 + "Compare focused database management against a full BaaS platform",
119 + "See when smbCloud GresIQ is the simpler fit and when Supabase still makes sense",
120 + ],
121 + },
122 + {
123 + href: "/planetscale-alternative",
124 + eyebrow: "GresIQ comparison",
125 + title: "PlanetScale alternative",
126 + description:
127 + "A database that ships with your app, no branching workflow required.",
128 + bullets: [
129 + "Compare database-per-project workflows against branch-based schema management",
130 + "Honest tradeoffs between simplicity and scale",
131 + ],
132 + },
133 + {
134 + href: "/neon-alternative",
135 + eyebrow: "GresIQ comparison",
136 + title: "Neon alternative",
137 + description:
138 + "Always-on Postgres without cold starts or serverless trade-offs.",
139 + bullets: [
140 + "Compare always-on managed databases against serverless Postgres",
141 + "See which fits better for teams that already use smbCloud Deploy or Auth",
142 + ],
143 + },
144 +];
145 +
146 const mailComparisonPages = [
147 {
148 href: "/resend-alternative",
149 eyebrow: "Mail comparison",
150 title: "Resend alternative",
120 - description:
121 - "If your problem starts with the inbox, not just the send API.",
151 + description: "Starts with the inbox, not the send API.",
152 bullets: [
153 "Compare inbox-first mail workflows against sending-first tools",
154 "See where smbCloud Mail fits for routing, forwarding, and stored inbound mail",
@@ -128,8 +158,7 @@ const mailComparisonPages = [
158 href: "/usesend-alternative",
159 eyebrow: "Mail comparison",
160 title: "useSend alternative",
131 - description:
132 - "If you want managed mail workflows instead of running the email stack yourself.",
161 + description: "Managed mail without running the stack yourself.",
162 bullets: [
163 "A cleaner fit for domain inbox routes and forwarding",
164 "Honest tradeoffs between hosted mail and open-source sending infrastructure",
@@ -139,8 +168,7 @@ const mailComparisonPages = [
168 href: "/sendgrid-alternative",
169 eyebrow: "Mail comparison",
170 title: "SendGrid alternative",
142 - description:
143 - "If you need domain inboxes and inbound routing more than a giant email suite.",
171 + description: "Domain inboxes and inbound routing without the giant suite.",
172 bullets: [
173 "Compare inbox routes, forwarding, and stored inbound mail against send-heavy tooling",
174 "See when smbCloud Mail is the better bet and when it is not",
@@ -216,31 +244,16 @@ export default function Home() {
244
245 <div className="mx-auto max-w-4xl text-center">
246 <p className="mb-8 text-sm font-semibold uppercase tracking-[0.2em] text-slate-500 dark:text-slate-500">
219 - Enterprise Power at Startup Velocity
247 + Start at the finish line: distribution.
248 </p>
249
250 <h1 className="text-5xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-7xl leading-[1.05]">
223 - Deploy your app without the{" "}
224 - <span className="text-indigo-600 dark:text-indigo-400">
225 - platform maze.
226 - </span>
251 + <ServiceWordCycler />{" "}
252 + <span className="text-slate-900 dark:text-white">your app.</span>
253 </h1>
254
229 - <p className="mx-auto mt-8 max-w-3xl text-lg leading-8 text-slate-600 dark:text-slate-400">
230 - smbCloud is an SSH-first deploy platform for small teams shipping{" "}
231 - <span className="font-semibold text-slate-800 dark:text-slate-200">
232 - Node.js
233 - </span>
234 - ,{" "}
235 - <span className="font-semibold text-slate-800 dark:text-slate-200">
236 - Ruby on Rails
237 - </span>
238 - , and{" "}
239 - <span className="font-semibold text-slate-800 dark:text-slate-200">
240 - Swift/Vapor
241 - </span>{" "}
242 - apps. Run one command, watch the logs, and roll back fast when you
243 - need to.
255 + <p className="mx-auto mt-8 max-w-2xl text-lg leading-8 text-slate-600 dark:text-slate-400">
256 + Don't just build. Deliver your <FrameworkWordCycler /> app.
257 </p>
258
259 <div className="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
@@ -259,41 +272,6 @@ export default function Home() {
272 Install the CLI
273 </Link>
274 </div>
262 -
263 - <div className="mt-5 flex flex-wrap items-center justify-center gap-x-4 gap-y-2 text-sm text-slate-500 dark:text-slate-400">
264 - <Link
265 - href="/heroku-alternative"
266 - className="font-medium text-indigo-600 transition-colors hover:text-indigo-500 dark:text-indigo-400"
267 - >
268 - Heroku alternative
269 - </Link>
270 - <span className="text-slate-300 dark:text-slate-700">•</span>
271 - <Link
272 - href="/railway-alternative"
273 - className="font-medium text-indigo-600 transition-colors hover:text-indigo-500 dark:text-indigo-400"
274 - >
275 - Railway alternative
276 - </Link>
277 - </div>
278 -
279 - <div className="mt-8 flex flex-wrap items-center justify-center gap-x-4 gap-y-2 text-xs font-medium text-slate-500 dark:text-slate-500">
280 - <span>Built by Splitfire AB</span>
281 - <span className="hidden sm:inline text-slate-300 dark:text-slate-700">
282 - •
283 - </span>
284 - <Link
285 - href="https://github.com/smbcloudXYZ"
286 - target="_blank"
287 - rel="noopener noreferrer"
288 - className="transition-colors hover:text-slate-800 dark:hover:text-slate-300"
289 - >
290 - Open source on GitHub
291 - </Link>
292 - <span className="hidden sm:inline text-slate-300 dark:text-slate-700">
293 - •
294 - </span>
295 - <span>Stockholm, Sweden</span>
296 - </div>
275 </div>
276 </section>
277
@@ -331,11 +309,10 @@ export default function Home() {
309 Why smbCloud
310 </p>
311 <h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-4xl">
334 - Most teams do not need more platform.
312 + Deploys should be boring.
313 </h2>
314 <p className="mt-5 text-base leading-8 text-slate-600 dark:text-slate-400">
337 - We kept coming back to the same idea: deploys should be easy to
338 - explain and hard to mess up. That is where smbCloud fits.
315 + Run `smb`. Watch the logs. Move on.
316 </p>
317 </div>
318
@@ -364,7 +341,7 @@ export default function Home() {
341 Who it fits best
342 </p>
343 <h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
367 - This is for teams like ours.
344 + Good fit
345 </h2>
346 <ul className="mt-6 space-y-4 text-sm leading-7 text-slate-600 dark:text-slate-400">
347 {bestFit.map((item) => (
@@ -381,7 +358,7 @@ export default function Home() {
358 Stack focus
359 </p>
360 <h2 className="mt-4 text-2xl font-bold tracking-tight text-slate-900 dark:text-white">
384 - The stacks we care about.
361 + Supported stacks
362 </h2>
363 <div className="mt-6 space-y-4 text-sm text-slate-600 dark:text-slate-400">
364 <div className="rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.03] p-4">
@@ -389,8 +366,7 @@ export default function Home() {
366 Node.js
367 </p>
368 <p className="mt-1 leading-7">
392 - A strong fit for web apps, APIs, dashboards, and backend
393 - services.
369 + Web apps, APIs, dashboards, backend services.
370 </p>
371 </div>
372 <div className="rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.03] p-4">
@@ -398,8 +374,7 @@ export default function Home() {
374 Ruby on Rails
375 </p>
376 <p className="mt-1 leading-7">
401 - Still one of my favorite stacks when I want to ship fast
402 - without arguing with the tooling.
377 + Still my favorite stack for shipping fast.
378 </p>
379 </div>
380 <div className="rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.03] p-4">
@@ -407,8 +382,7 @@ export default function Home() {
382 Swift / Vapor
383 </p>
384 <p className="mt-1 leading-7">
410 - A good fit if you like Vapor and do not want to be treated
411 - like an afterthought.
385 + Works. Doesn't treat Vapor as an afterthought.
386 </p>
387 </div>
388 </div>
@@ -423,7 +397,7 @@ export default function Home() {
397 Compare services
398 </p>
399 <h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-4xl">
426 - Start with the service you are actually choosing.
400 + Pick the service you're comparing.
401 </h2>
402 </div>
403
@@ -434,12 +408,11 @@ export default function Home() {
408 smbCloud Deploy
409 </p>
410 <h3 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
437 - Looking at Heroku or Railway?
411 + Heroku or Railway?
412 </h3>
413 <p className="mt-4 text-base leading-8 text-slate-600 dark:text-slate-400">
440 - Fair enough. We wrote the comparison pages I would actually
441 - want to read before switching: direct, specific, and honest
442 - about fit.
414 + Direct comparisons, honest about where smbCloud fits and where
415 + it doesn't.
416 </p>
417 </div>
418
@@ -481,12 +454,10 @@ export default function Home() {
454 smbCloud Auth
455 </p>
456 <h3 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
484 - Looking at Auth0, Clerk, or Supabase Auth?
457 + Auth0, Clerk, or Supabase Auth?
458 </h3>
459 <p className="mt-4 text-base leading-8 text-slate-600 dark:text-slate-400">
487 - That is the smbCloud Auth side of the business. Some teams
488 - want a giant identity stack. Others just want sign-in, signup,
489 - sessions, and hosted auth flows that stay out of the way.
460 + Sign-in, signup, sessions. Hosted, out of the way.
461 </p>
462 </div>
463
@@ -528,13 +499,10 @@ export default function Home() {
499 smbCloud Mail
500 </p>
501 <h3 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
531 - Looking at Resend, useSend, or SendGrid?
502 + Resend, useSend, or SendGrid?
503 </h3>
504 <p className="mt-4 text-base leading-8 text-slate-600 dark:text-slate-400">
534 - That is the smbCloud Mail side of the business. Most email
535 - tools start with sending. We started with the inbox: domain
536 - routes, forwarding, and keeping the messages that actually
537 - came in.
505 + Domain routes, forwarding, stored inbound mail.
506 </p>
507 </div>
508
@@ -569,6 +537,50 @@ export default function Home() {
537 ))}
538 </div>
539 </div>
540 + <div className="border-t border-slate-200 pt-16 dark:border-white/[0.06]">
541 + <div className="mb-10 max-w-3xl">
542 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
543 + smbCloud GresIQ
544 + </p>
545 + <h3 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
546 + Supabase, PlanetScale, or Neon?
547 + </h3>
548 + <p className="mt-4 text-base leading-8 text-slate-600 dark:text-slate-400">
549 + A data layer that lives with your stack, not next to it.
550 + </p>
551 + </div>
552 +
553 + <div className="grid gap-6 lg:grid-cols-3">
554 + {gresiqComparisonPages.map((page) => (
555 + <Link
556 + key={page.href}
557 + href={page.href}
558 + className="group rounded-2xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.02] p-8 transition-colors hover:border-indigo-300 dark:hover:border-indigo-700/50 hover:bg-slate-50 dark:hover:bg-white/[0.04]"
559 + >
560 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
561 + {page.eyebrow}
562 + </p>
563 + <h4 className="mt-4 text-2xl font-semibold tracking-tight text-slate-900 transition-colors group-hover:text-indigo-600 dark:text-white dark:group-hover:text-indigo-400">
564 + {page.title}
565 + </h4>
566 + <p className="mt-4 text-sm leading-7 text-slate-600 dark:text-slate-400">
567 + {page.description}
568 + </p>
569 + <ul className="mt-5 space-y-3 text-sm leading-6 text-slate-500 dark:text-slate-400">
570 + {page.bullets.map((bullet) => (
571 + <li key={bullet} className="flex gap-3">
572 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-500" />
573 + <span>{bullet}</span>
574 + </li>
575 + ))}
576 + </ul>
577 + <p className="mt-6 text-sm font-semibold text-indigo-600 dark:text-indigo-400">
578 + Read comparison →
579 + </p>
580 + </Link>
581 + ))}
582 + </div>
583 + </div>
584 </div>
585 </div>
586 </section>
@@ -578,11 +590,10 @@ export default function Home() {
590 <div className="relative overflow-hidden rounded-2xl bg-indigo-600 px-8 py-14 text-center shadow-2xl shadow-indigo-500/20">
591 <div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(255,255,255,0.1),transparent)]" />
592 <h2 className="relative text-3xl font-bold tracking-tight text-white">
581 - Ready to ship?
593 + Try it on one app.
594 </h2>
595 <p className="relative mt-4 text-indigo-200">
584 - Create an account, install the CLI, and see if it feels better on
585 - a real project.
596 + Create an account and install the CLI.
597 </p>
598 <div className="relative mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
599 <Link
@@ -662,9 +673,8 @@ export default function Home() {
673 </div>
674
675 <p className="mb-2 max-w-md text-sm leading-relaxed text-slate-300 dark:text-slate-400">
665 - <span className="font-semibold text-white">smbcloud-cli</span>,
666 - turned into a native app. Review changes, make quick edits, and
667 - trigger deploys when you are away from your laptop.
676 + <span className="font-semibold text-white">smbcloud-cli</span> as
677 + a native app. Review changes, edit, and deploy from your phone.
678 </p>
679 <p className="mb-8 text-xs italic text-slate-500">
680 Available on iPhone, Android, and Windows.
app/planetscale-alternative/page.tsx new
+366
@@ -0,0 +1,366 @@
1 +import type { Metadata } from "next";
2 +import Link from "next/link";
3 +import { StructuredData } from "@/components/structured-data";
4 +import { absoluteUrl, genPageMetadata } from "../seo";
5 +
6 +const comparisonRows = [
7 + {
8 + label: "What it is",
9 + smbcloud:
10 + "A data layer service managed inside your smbCloud project, alongside Deploy, Auth, and Mail.",
11 + planetscale:
12 + "A MySQL-compatible serverless database platform with a Git-like schema branching workflow.",
13 + },
14 + {
15 + label: "Best fit",
16 + smbcloud:
17 + "Teams that want database management without a separate platform or schema branch workflow.",
18 + planetscale:
19 + "Teams that need a highly scalable MySQL-compatible database with developer-friendly schema migrations.",
20 + },
21 + {
22 + label: "Schema workflow",
23 + smbcloud:
24 + "Standard migration tooling that comes with your app framework. No separate branching required.",
25 + planetscale:
26 + "Branch-based schema changes with non-blocking migrations as a core feature.",
27 + },
28 + {
29 + label: "Platform scope",
30 + smbcloud:
31 + "Narrow. The data layer is one service in a broader stack, not a standalone product.",
32 + planetscale:
33 + "Database-first. Deep investment in schema management, analytics, and scaling features.",
34 + },
35 + {
36 + label: "Why teams pick it",
37 + smbcloud:
38 + "Because they want less tooling to learn and a database that fits naturally into one platform.",
39 + planetscale:
40 + "Because they want best-in-class MySQL scalability and a thoughtful schema branching story.",
41 + },
42 +];
43 +
44 +const smbcloudFits = [
45 + "You want database management inside the same platform you use for deploys and auth.",
46 + "You do not need MySQL-specific features or PlanetScale's branching workflow.",
47 + "You prefer fewer specialised tools and a smaller operational footprint.",
48 + "Your app uses PostgreSQL or a standard relational setup.",
49 +];
50 +
51 +const planetscaleStillFits = [
52 + "You need MySQL-compatible databases at serious scale.",
53 + "Your team relies on schema branching and non-blocking migrations.",
54 + "You want a database-first platform with deep analytics and insight tooling.",
55 + "You are already using PlanetScale's SDK or Serverless Driver for edge deployments.",
56 +];
57 +
58 +const faqs = [
59 + {
60 + question: "Is smbCloud GresIQ a PlanetScale replacement?",
61 + answer:
62 + "For teams that want a simpler data layer without MySQL or schema branching, it can be. If you only used PlanetScale as a managed relational database and never leaned on its branching model, GresIQ is worth evaluating.",
63 + },
64 + {
65 + question: "Does GresIQ support MySQL?",
66 + answer:
67 + "GresIQ is focused on relational database management and is built to work alongside the smbCloud stack. It is not a MySQL-specific platform the way PlanetScale is.",
68 + },
69 + {
70 + question: "What happens to my schema migrations with GresIQ?",
71 + answer:
72 + "You use your framework's standard migration tooling. GresIQ handles connection and service management — it does not add a new migration layer on top of your existing workflow.",
73 + },
74 + {
75 + question: "Who should consider switching from PlanetScale to GresIQ?",
76 + answer:
77 + "Teams using PlanetScale mainly as a hosted relational database who do not depend on its branching model and would prefer to consolidate tooling under one platform.",
78 + },
79 +];
80 +
81 +export const metadata: Metadata = {
82 + ...genPageMetadata({
83 + title: "PlanetScale alternative for teams that want less tooling",
84 + description:
85 + "Looking for a PlanetScale alternative? smbCloud GresIQ manages the data layer inside the same platform as your app, auth, and mail — no schema branching workflow required.",
86 + path: "/planetscale-alternative",
87 + keywords: [
88 + "PlanetScale alternative",
89 + "PlanetScale replacement",
90 + "managed database service",
91 + "GresIQ database",
92 + "smbCloud GresIQ",
93 + "database without branching",
94 + "relational database platform",
95 + ],
96 + }),
97 +};
98 +
99 +const structuredData = {
100 + "@context": "https://schema.org",
101 + "@graph": [
102 + {
103 + "@type": "WebPage",
104 + "@id": absoluteUrl("/planetscale-alternative#webpage"),
105 + url: absoluteUrl("/planetscale-alternative"),
106 + name: "PlanetScale alternative for teams that want less tooling",
107 + description:
108 + "Compare smbCloud GresIQ and PlanetScale for teams that want a simpler data layer without branch-based schema management.",
109 + isPartOf: {
110 + "@id": absoluteUrl("/#website"),
111 + },
112 + },
113 + {
114 + "@type": "BreadcrumbList",
115 + itemListElement: [
116 + {
117 + "@type": "ListItem",
118 + position: 1,
119 + name: "Home",
120 + item: absoluteUrl("/"),
121 + },
122 + {
123 + "@type": "ListItem",
124 + position: 2,
125 + name: "PlanetScale alternative",
126 + item: absoluteUrl("/planetscale-alternative"),
127 + },
128 + ],
129 + },
130 + {
131 + "@type": "FAQPage",
132 + mainEntity: faqs.map((faq) => ({
133 + "@type": "Question",
134 + name: faq.question,
135 + acceptedAnswer: {
136 + "@type": "Answer",
137 + text: faq.answer,
138 + },
139 + })),
140 + },
141 + ],
142 +};
143 +
144 +export default function PlanetScaleAlternativePage() {
145 + return (
146 + <div>
147 + <StructuredData data={structuredData} />
148 +
149 + <section className="relative overflow-hidden px-6 pt-24 pb-16 sm:pt-32 sm:pb-20 lg:px-8">
150 + <div className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[520px] bg-[radial-gradient(ellipse_75%_50%_at_50%_0%,rgba(99,102,241,0.18),transparent)] dark:bg-[radial-gradient(ellipse_75%_50%_at_50%_0%,rgba(99,102,241,0.12),transparent)]" />
151 +
152 + <div className="mx-auto max-w-4xl">
153 + <div className="mb-6 inline-flex items-center gap-2 rounded-full border border-indigo-200 bg-indigo-50 px-4 py-1.5 dark:border-indigo-500/30 dark:bg-indigo-500/10">
154 + <span className="h-1.5 w-1.5 rounded-full bg-indigo-500" />
155 + <span className="text-xs font-semibold uppercase tracking-widest text-indigo-700 dark:text-indigo-400">
156 + GresIQ comparison
157 + </span>
158 + </div>
159 +
160 + <h1 className="max-w-3xl text-4xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-6xl leading-[1.05]">
161 + Looking for a PlanetScale alternative?
162 + </h1>
163 +
164 + <p className="mt-6 max-w-3xl text-lg leading-8 text-slate-600 dark:text-slate-400">
165 + PlanetScale is a serious platform for teams that need MySQL at
166 + scale. But most teams do not need branch-based schema management.
167 + smbCloud GresIQ manages the data layer inside the same platform as
168 + your app, auth, and mail — with your normal migration workflow and
169 + nothing extra to learn.
170 + </p>
171 +
172 + <div className="mt-8 flex flex-col gap-4 sm:flex-row">
173 + <Link
174 + href="https://console.smbcloud.xyz"
175 + target="_blank"
176 + rel="noopener noreferrer"
177 + className="inline-flex items-center justify-center rounded-lg bg-indigo-600 px-8 py-3.5 text-sm font-semibold text-white shadow-lg shadow-indigo-500/20 transition-all hover:bg-indigo-500 hover:shadow-indigo-500/30 active:scale-[0.98]"
178 + >
179 + Try smbCloud GresIQ
180 + </Link>
181 + <Link
182 + href="/services"
183 + className="inline-flex items-center justify-center rounded-lg border border-slate-300 px-8 py-3.5 text-sm font-semibold text-slate-700 transition-colors hover:bg-slate-50 dark:border-white/[0.12] dark:text-slate-200 dark:hover:bg-white/[0.04]"
184 + >
185 + See all services
186 + </Link>
187 + </div>
188 + </div>
189 + </section>
190 +
191 + <section className="px-6 pb-24 lg:px-8">
192 + <div className="mx-auto max-w-5xl grid gap-8 lg:grid-cols-[0.95fr_1.05fr]">
193 + <div>
194 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
195 + Why teams look beyond PlanetScale
196 + </p>
197 + <h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
198 + Usually they needed a managed database. The branching came along
199 + for the ride.
200 + </h2>
201 + <p className="mt-5 text-base leading-8 text-slate-600 dark:text-slate-400">
202 + PlanetScale built something genuinely impressive around schema
203 + branching. But for most small teams shipping Rails, Node.js, or
204 + Vapor apps, standard migrations already work fine. GresIQ skips
205 + the extra model and keeps the database close to the rest of your
206 + stack.
207 + </p>
208 + </div>
209 +
210 + <div className="rounded-2xl border border-slate-200 bg-slate-50 p-8 dark:border-white/[0.06] dark:bg-white/[0.02]">
211 + <p className="text-sm font-semibold text-slate-900 dark:text-white">
212 + What GresIQ does well
213 + </p>
214 + <ul className="mt-5 space-y-4 text-sm leading-7 text-slate-600 dark:text-slate-400">
215 + <li className="flex gap-3">
216 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-500" />
217 + <span>Database service management inside the same platform as your app and auth.</span>
218 + </li>
219 + <li className="flex gap-3">
220 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-500" />
221 + <span>No new migration model. Use the tooling that ships with your framework.</span>
222 + </li>
223 + <li className="flex gap-3">
224 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-500" />
225 + <span>Smaller surface area and fewer accounts to manage.</span>
226 + </li>
227 + </ul>
228 + </div>
229 + </div>
230 + </section>
231 +
232 + <section className="border-t border-slate-200 px-6 py-24 dark:border-white/[0.06] lg:px-8">
233 + <div className="mx-auto max-w-5xl">
234 + <div className="mb-12 max-w-3xl">
235 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
236 + smbCloud GresIQ vs PlanetScale
237 + </p>
238 + <h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
239 + Here is the trade, without the usual sales fog.
240 + </h2>
241 + </div>
242 +
243 + <div className="overflow-hidden rounded-2xl border border-slate-200 dark:border-white/[0.06]">
244 + <div className="grid grid-cols-[0.8fr_1fr_1fr] bg-slate-50 text-sm font-semibold text-slate-900 dark:bg-white/[0.03] dark:text-white">
245 + <div className="border-b border-slate-200 px-5 py-4 dark:border-white/[0.06]">
246 + Area
247 + </div>
248 + <div className="border-b border-l border-slate-200 px-5 py-4 dark:border-white/[0.06]">
249 + smbCloud GresIQ
250 + </div>
251 + <div className="border-b border-l border-slate-200 px-5 py-4 dark:border-white/[0.06]">
252 + PlanetScale
253 + </div>
254 + </div>
255 + {comparisonRows.map((row) => (
256 + <div
257 + key={row.label}
258 + className="grid grid-cols-[0.8fr_1fr_1fr] text-sm leading-7 text-slate-600 dark:text-slate-400"
259 + >
260 + <div className="border-b border-slate-200 bg-white px-5 py-5 font-semibold text-slate-900 dark:border-white/[0.06] dark:bg-white/[0.02] dark:text-white">
261 + {row.label}
262 + </div>
263 + <div className="border-b border-l border-slate-200 bg-white px-5 py-5 dark:border-white/[0.06] dark:bg-white/[0.02]">
264 + {row.smbcloud}
265 + </div>
266 + <div className="border-b border-l border-slate-200 bg-white px-5 py-5 dark:border-white/[0.06] dark:bg-white/[0.02]">
267 + {row.planetscale}
268 + </div>
269 + </div>
270 + ))}
271 + </div>
272 + </div>
273 + </section>
274 +
275 + <section className="px-6 py-24 lg:px-8">
276 + <div className="mx-auto grid max-w-5xl gap-6 lg:grid-cols-2">
277 + <div className="rounded-2xl border border-slate-200 bg-white p-8 dark:border-white/[0.06] dark:bg-white/[0.02]">
278 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
279 + Pick smbCloud GresIQ if
280 + </p>
281 + <ul className="mt-6 space-y-4 text-sm leading-7 text-slate-600 dark:text-slate-400">
282 + {smbcloudFits.map((item) => (
283 + <li key={item} className="flex gap-3">
284 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-500" />
285 + <span>{item}</span>
286 + </li>
287 + ))}
288 + </ul>
289 + </div>
290 +
291 + <div className="rounded-2xl border border-slate-200 bg-slate-50 p-8 dark:border-white/[0.06] dark:bg-white/[0.02]">
292 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
293 + Stay on PlanetScale if
294 + </p>
295 + <ul className="mt-6 space-y-4 text-sm leading-7 text-slate-600 dark:text-slate-400">
296 + {planetscaleStillFits.map((item) => (
297 + <li key={item} className="flex gap-3">
298 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-slate-400" />
299 + <span>{item}</span>
300 + </li>
301 + ))}
302 + </ul>
303 + </div>
304 + </div>
305 + </section>
306 +
307 + <section className="px-6 pb-24 lg:px-8">
308 + <div className="mx-auto max-w-3xl">
309 + <div className="mb-10">
310 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
311 + FAQ
312 + </p>
313 + <h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
314 + Common questions
315 + </h2>
316 + </div>
317 + <div className="space-y-4">
318 + {faqs.map((faq) => (
319 + <div
320 + key={faq.question}
321 + className="rounded-2xl border border-slate-200 bg-white p-6 dark:border-white/[0.06] dark:bg-white/[0.02]"
322 + >
323 + <h3 className="text-lg font-semibold text-slate-900 dark:text-white">
324 + {faq.question}
325 + </h3>
326 + <p className="mt-3 text-sm leading-7 text-slate-600 dark:text-slate-400">
327 + {faq.answer}
328 + </p>
329 + </div>
330 + ))}
331 + </div>
332 + </div>
333 + </section>
334 +
335 + <section className="px-6 pb-20 lg:px-8">
336 + <div className="mx-auto max-w-3xl">
337 + <div className="relative overflow-hidden rounded-2xl bg-indigo-600 px-8 py-14 text-center shadow-2xl shadow-indigo-500/20">
338 + <div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(255,255,255,0.1),transparent)]" />
339 + <h2 className="relative text-3xl font-bold tracking-tight text-white">
340 + If PlanetScale feels like more than you need, try GresIQ.
341 + </h2>
342 + <p className="relative mt-4 text-indigo-200">
343 + Run it on one project. That is usually enough to know.
344 + </p>
345 + <div className="relative mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
346 + <Link
347 + href="https://console.smbcloud.xyz"
348 + target="_blank"
349 + rel="noopener noreferrer"
350 + className="inline-flex rounded-lg bg-white px-8 py-3.5 text-sm font-semibold text-indigo-700 shadow-sm transition-colors hover:bg-indigo-50"
351 + >
352 + Start with smbCloud
353 + </Link>
354 + <Link
355 + href="/neon-alternative"
356 + className="inline-flex rounded-lg border border-white/20 px-8 py-3.5 text-sm font-semibold text-white transition-colors hover:bg-white/10"
357 + >
358 + Compare Neon alternative
359 + </Link>
360 + </div>
361 + </div>
362 + </div>
363 + </section>
364 + </div>
365 + );
366 +}
app/supabase-alternative/page.tsx new
+363
@@ -0,0 +1,363 @@
1 +import type { Metadata } from "next";
2 +import Link from "next/link";
3 +import { StructuredData } from "@/components/structured-data";
4 +import { absoluteUrl, genPageMetadata } from "../seo";
5 +
6 +const comparisonRows = [
7 + {
8 + label: "What it is",
9 + smbcloud:
10 + "A data layer service that lives inside your smbCloud project alongside Deploy, Auth, and Mail.",
11 + supabase:
12 + "A full BaaS platform: Postgres, auth, storage, realtime, edge functions — all bundled together.",
13 + },
14 + {
15 + label: "Best fit",
16 + smbcloud:
17 + "Teams that already use smbCloud and want the database managed in the same place.",
18 + supabase:
19 + "Teams that want a Firebase-like platform on top of Postgres and are happy to use the full suite.",
20 + },
21 + {
22 + label: "Platform scope",
23 + smbcloud:
24 + "Narrow on purpose. Manages the data layer without becoming a platform of its own.",
25 + supabase:
26 + "Broad. Auth, storage, and realtime are first-class citizens alongside the database.",
27 + },
28 + {
29 + label: "Auth story",
30 + smbcloud:
31 + "Auth is a separate smbCloud service. You pick what you need.",
32 + supabase:
33 + "Auth is built in. Supabase Auth ships with every project by default.",
34 + },
35 + {
36 + label: "Why teams pick it",
37 + smbcloud:
38 + "Because they want less surface area and want the data layer close to the rest of the stack.",
39 + supabase:
40 + "Because they want one product that covers most backend needs out of the box.",
41 + },
42 +];
43 +
44 +const smbcloudFits = [
45 + "You already use smbCloud Deploy or Auth and want the data layer in the same platform.",
46 + "You want a focused data service, not a full BaaS.",
47 + "You do not need Supabase Auth, Storage, or Realtime alongside your database.",
48 + "You prefer fewer moving parts and a smaller blast radius when something goes wrong.",
49 +];
50 +
51 +const supabaseStillFits = [
52 + "You want Firebase-style convenience on top of Postgres.",
53 + "You need auth, storage, and realtime out of the box.",
54 + "Your team is already building around the Supabase SDK and tooling.",
55 + "You want a generous free tier with Postgres included.",
56 +];
57 +
58 +const faqs = [
59 + {
60 + question: "Is smbCloud GresIQ a Supabase replacement?",
61 + answer:
62 + "For teams that only need the data layer, it can be. If you are using Supabase mainly for its database and find the rest of the platform growing into something you manage instead of use, GresIQ is worth a look.",
63 + },
64 + {
65 + question: "Does smbCloud GresIQ include auth or storage?",
66 + answer:
67 + "No. Auth is a separate smbCloud service. GresIQ focuses on the data layer. That is intentional — you add what you need instead of inheriting everything.",
68 + },
69 + {
70 + question: "What type of database does GresIQ manage?",
71 + answer:
72 + "GresIQ is built for relational databases and is designed to run alongside the rest of the smbCloud stack in the same platform.",
73 + },
74 + {
75 + question: "Who should consider switching from Supabase to GresIQ?",
76 + answer:
77 + "Teams using Supabase mainly as a managed Postgres instance who want fewer abstractions and a data layer that stays inside one platform with their app and auth.",
78 + },
79 +];
80 +
81 +export const metadata: Metadata = {
82 + ...genPageMetadata({
83 + title: "Supabase alternative for teams that want less BaaS",
84 + description:
85 + "Looking for a Supabase alternative? smbCloud GresIQ is a focused data layer that lives inside the same platform as your app, auth, and mail — without the full BaaS overhead.",
86 + path: "/supabase-alternative",
87 + keywords: [
88 + "Supabase alternative",
89 + "Supabase replacement",
90 + "managed database platform",
91 + "GresIQ database",
92 + "smbCloud GresIQ",
93 + "data layer service",
94 + "Postgres alternative",
95 + ],
96 + }),
97 +};
98 +
99 +const structuredData = {
100 + "@context": "https://schema.org",
101 + "@graph": [
102 + {
103 + "@type": "WebPage",
104 + "@id": absoluteUrl("/supabase-alternative#webpage"),
105 + url: absoluteUrl("/supabase-alternative"),
106 + name: "Supabase alternative for teams that want less BaaS",
107 + description:
108 + "Compare smbCloud GresIQ and Supabase for teams that want a focused data layer without the full BaaS platform.",
109 + isPartOf: {
110 + "@id": absoluteUrl("/#website"),
111 + },
112 + },
113 + {
114 + "@type": "BreadcrumbList",
115 + itemListElement: [
116 + {
117 + "@type": "ListItem",
118 + position: 1,
119 + name: "Home",
120 + item: absoluteUrl("/"),
121 + },
122 + {
123 + "@type": "ListItem",
124 + position: 2,
125 + name: "Supabase alternative",
126 + item: absoluteUrl("/supabase-alternative"),
127 + },
128 + ],
129 + },
130 + {
131 + "@type": "FAQPage",
132 + mainEntity: faqs.map((faq) => ({
133 + "@type": "Question",
134 + name: faq.question,
135 + acceptedAnswer: {
136 + "@type": "Answer",
137 + text: faq.answer,
138 + },
139 + })),
140 + },
141 + ],
142 +};
143 +
144 +export default function SupabaseAlternativePage() {
145 + return (
146 + <div>
147 + <StructuredData data={structuredData} />
148 +
149 + <section className="relative overflow-hidden px-6 pt-24 pb-16 sm:pt-32 sm:pb-20 lg:px-8">
150 + <div className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[520px] bg-[radial-gradient(ellipse_75%_50%_at_50%_0%,rgba(99,102,241,0.18),transparent)] dark:bg-[radial-gradient(ellipse_75%_50%_at_50%_0%,rgba(99,102,241,0.12),transparent)]" />
151 +
152 + <div className="mx-auto max-w-4xl">
153 + <div className="mb-6 inline-flex items-center gap-2 rounded-full border border-indigo-200 bg-indigo-50 px-4 py-1.5 dark:border-indigo-500/30 dark:bg-indigo-500/10">
154 + <span className="h-1.5 w-1.5 rounded-full bg-indigo-500" />
155 + <span className="text-xs font-semibold uppercase tracking-widest text-indigo-700 dark:text-indigo-400">
156 + GresIQ comparison
157 + </span>
158 + </div>
159 +
160 + <h1 className="max-w-3xl text-4xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-6xl leading-[1.05]">
161 + Looking for a Supabase alternative?
162 + </h1>
163 +
164 + <p className="mt-6 max-w-3xl text-lg leading-8 text-slate-600 dark:text-slate-400">
165 + Supabase does a lot. That is exactly why some teams want something
166 + smaller. smbCloud GresIQ is a data layer that lives inside the same
167 + platform as your app, auth, and mail — without growing into its own
168 + product you have to manage on top of everything else.
169 + </p>
170 +
171 + <div className="mt-8 flex flex-col gap-4 sm:flex-row">
172 + <Link
173 + href="https://console.smbcloud.xyz"
174 + target="_blank"
175 + rel="noopener noreferrer"
176 + className="inline-flex items-center justify-center rounded-lg bg-indigo-600 px-8 py-3.5 text-sm font-semibold text-white shadow-lg shadow-indigo-500/20 transition-all hover:bg-indigo-500 hover:shadow-indigo-500/30 active:scale-[0.98]"
177 + >
178 + Try smbCloud GresIQ
179 + </Link>
180 + <Link
181 + href="/services"
182 + className="inline-flex items-center justify-center rounded-lg border border-slate-300 px-8 py-3.5 text-sm font-semibold text-slate-700 transition-colors hover:bg-slate-50 dark:border-white/[0.12] dark:text-slate-200 dark:hover:bg-white/[0.04]"
183 + >
184 + See all services
185 + </Link>
186 + </div>
187 + </div>
188 + </section>
189 +
190 + <section className="px-6 pb-24 lg:px-8">
191 + <div className="mx-auto max-w-5xl grid gap-8 lg:grid-cols-[0.95fr_1.05fr]">
192 + <div>
193 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
194 + Why teams look beyond Supabase
195 + </p>
196 + <h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
197 + Usually they only needed the database. The rest came along anyway.
198 + </h2>
199 + <p className="mt-5 text-base leading-8 text-slate-600 dark:text-slate-400">
200 + Supabase is generous and well-built. But when you just need a
201 + managed data layer, inheriting auth, storage, and realtime
202 + alongside it adds surface area you did not ask for and will
203 + eventually have to understand.
204 + </p>
205 + </div>
206 +
207 + <div className="rounded-2xl border border-slate-200 bg-slate-50 p-8 dark:border-white/[0.06] dark:bg-white/[0.02]">
208 + <p className="text-sm font-semibold text-slate-900 dark:text-white">
209 + What GresIQ does well
210 + </p>
211 + <ul className="mt-5 space-y-4 text-sm leading-7 text-slate-600 dark:text-slate-400">
212 + <li className="flex gap-3">
213 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-500" />
214 + <span>Database management inside the same platform as your app and auth.</span>
215 + </li>
216 + <li className="flex gap-3">
217 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-500" />
218 + <span>Connection and service management without a separate dashboard to learn.</span>
219 + </li>
220 + <li className="flex gap-3">
221 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-500" />
222 + <span>Built to stay focused — a data layer, not a backend platform.</span>
223 + </li>
224 + </ul>
225 + </div>
226 + </div>
227 + </section>
228 +
229 + <section className="border-t border-slate-200 px-6 py-24 dark:border-white/[0.06] lg:px-8">
230 + <div className="mx-auto max-w-5xl">
231 + <div className="mb-12 max-w-3xl">
232 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
233 + smbCloud GresIQ vs Supabase
234 + </p>
235 + <h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
236 + Here is the trade, without the usual sales fog.
237 + </h2>
238 + </div>
239 +
240 + <div className="overflow-hidden rounded-2xl border border-slate-200 dark:border-white/[0.06]">
241 + <div className="grid grid-cols-[0.8fr_1fr_1fr] bg-slate-50 text-sm font-semibold text-slate-900 dark:bg-white/[0.03] dark:text-white">
242 + <div className="border-b border-slate-200 px-5 py-4 dark:border-white/[0.06]">
243 + Area
244 + </div>
245 + <div className="border-b border-l border-slate-200 px-5 py-4 dark:border-white/[0.06]">
246 + smbCloud GresIQ
247 + </div>
248 + <div className="border-b border-l border-slate-200 px-5 py-4 dark:border-white/[0.06]">
249 + Supabase
250 + </div>
251 + </div>
252 + {comparisonRows.map((row) => (
253 + <div
254 + key={row.label}
255 + className="grid grid-cols-[0.8fr_1fr_1fr] text-sm leading-7 text-slate-600 dark:text-slate-400"
256 + >
257 + <div className="border-b border-slate-200 bg-white px-5 py-5 font-semibold text-slate-900 dark:border-white/[0.06] dark:bg-white/[0.02] dark:text-white">
258 + {row.label}
259 + </div>
260 + <div className="border-b border-l border-slate-200 bg-white px-5 py-5 dark:border-white/[0.06] dark:bg-white/[0.02]">
261 + {row.smbcloud}
262 + </div>
263 + <div className="border-b border-l border-slate-200 bg-white px-5 py-5 dark:border-white/[0.06] dark:bg-white/[0.02]">
264 + {row.supabase}
265 + </div>
266 + </div>
267 + ))}
268 + </div>
269 + </div>
270 + </section>
271 +
272 + <section className="px-6 py-24 lg:px-8">
273 + <div className="mx-auto grid max-w-5xl gap-6 lg:grid-cols-2">
274 + <div className="rounded-2xl border border-slate-200 bg-white p-8 dark:border-white/[0.06] dark:bg-white/[0.02]">
275 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
276 + Pick smbCloud GresIQ if
277 + </p>
278 + <ul className="mt-6 space-y-4 text-sm leading-7 text-slate-600 dark:text-slate-400">
279 + {smbcloudFits.map((item) => (
280 + <li key={item} className="flex gap-3">
281 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-indigo-500" />
282 + <span>{item}</span>
283 + </li>
284 + ))}
285 + </ul>
286 + </div>
287 +
288 + <div className="rounded-2xl border border-slate-200 bg-slate-50 p-8 dark:border-white/[0.06] dark:bg-white/[0.02]">
289 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
290 + Stay on Supabase if
291 + </p>
292 + <ul className="mt-6 space-y-4 text-sm leading-7 text-slate-600 dark:text-slate-400">
293 + {supabaseStillFits.map((item) => (
294 + <li key={item} className="flex gap-3">
295 + <span className="mt-2 h-1.5 w-1.5 shrink-0 rounded-full bg-slate-400" />
296 + <span>{item}</span>
297 + </li>
298 + ))}
299 + </ul>
300 + </div>
301 + </div>
302 + </section>
303 +
304 + <section className="px-6 pb-24 lg:px-8">
305 + <div className="mx-auto max-w-3xl">
306 + <div className="mb-10">
307 + <p className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
308 + FAQ
309 + </p>
310 + <h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
311 + Common questions
312 + </h2>
313 + </div>
314 + <div className="space-y-4">
315 + {faqs.map((faq) => (
316 + <div
317 + key={faq.question}
318 + className="rounded-2xl border border-slate-200 bg-white p-6 dark:border-white/[0.06] dark:bg-white/[0.02]"
319 + >
320 + <h3 className="text-lg font-semibold text-slate-900 dark:text-white">
321 + {faq.question}
322 + </h3>
323 + <p className="mt-3 text-sm leading-7 text-slate-600 dark:text-slate-400">
324 + {faq.answer}
325 + </p>
326 + </div>
327 + ))}
328 + </div>
329 + </div>
330 + </section>
331 +
332 + <section className="px-6 pb-20 lg:px-8">
333 + <div className="mx-auto max-w-3xl">
334 + <div className="relative overflow-hidden rounded-2xl bg-indigo-600 px-8 py-14 text-center shadow-2xl shadow-indigo-500/20">
335 + <div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(255,255,255,0.1),transparent)]" />
336 + <h2 className="relative text-3xl font-bold tracking-tight text-white">
337 + If Supabase feels like more platform than you need, try GresIQ.
338 + </h2>
339 + <p className="relative mt-4 text-indigo-200">
340 + Run it on one project. That is usually enough to know.
341 + </p>
342 + <div className="relative mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
343 + <Link
344 + href="https://console.smbcloud.xyz"
345 + target="_blank"
346 + rel="noopener noreferrer"
347 + className="inline-flex rounded-lg bg-white px-8 py-3.5 text-sm font-semibold text-indigo-700 shadow-sm transition-colors hover:bg-indigo-50"
348 + >
349 + Start with smbCloud
350 + </Link>
351 + <Link
352 + href="/planetscale-alternative"
353 + className="inline-flex rounded-lg border border-white/20 px-8 py-3.5 text-sm font-semibold text-white transition-colors hover:bg-white/10"
354 + >
355 + Compare PlanetScale alternative
356 + </Link>
357 + </div>
358 + </div>
359 + </div>
360 + </section>
361 + </div>
362 + );
363 +}
components/gt
components/header.tsx
+92
@@ -1,9 +1,15 @@
1 +"use client";
2 +
3 import Link from "next/link";
4 import { ModeToggle } from "./mode-toggle";
5 +import { useState } from "react";
6
7 export default function Header() {
8 + const [menuOpen, setMenuOpen] = useState(false);
9 +
10 return (
11 <header className="sticky top-0 z-50 border-b border-slate-200/60 dark:border-white/[0.06] bg-white/80 dark:bg-[#0A0E14]/90 backdrop-blur-md">
12 + {/* Announcement banner */}
13 <div className="border-b border-slate-200/60 bg-slate-900 text-white dark:border-white/[0.06] dark:bg-[#11162A]">
14 <div className="mx-auto flex max-w-5xl items-center justify-center px-6 py-2 text-center text-[11px] font-medium tracking-wide text-slate-200 lg:px-8">
15 <Link
@@ -16,8 +22,11 @@ export default function Header() {
22 </Link>
23 </div>
24 </div>
25 +
26 + {/* Main nav bar */}
27 <div className="mx-auto max-w-5xl px-6 lg:px-8">
28 <div className="flex h-14 items-center justify-between">
29 + {/* Logo */}
30 <Link href="/" className="flex items-center gap-2.5">
31 <div className="flex h-7 w-[38px] items-center justify-center rounded-md bg-indigo-600 text-white">
32 <span className="text-[10px] font-bold tracking-tight leading-none">
@@ -29,6 +38,7 @@ export default function Header() {
38 </span>
39 </Link>
40
41 + {/* Desktop nav */}
42 <nav className="hidden sm:flex items-center gap-6 text-sm font-medium text-slate-500 dark:text-slate-400">
43 <Link
44 href="/"
@@ -44,8 +54,11 @@ export default function Header() {
54 </Link>
55 </nav>
56
57 + {/* Right side */}
58 <div className="flex items-center gap-3">
59 <ModeToggle />
60 +
61 + {/* Desktop CTA */}
62 <Link
63 href="https://console.smbcloud.xyz"
64 target="_blank"
@@ -54,9 +67,88 @@ export default function Header() {
67 >
68 Create account →
69 </Link>
70 +
71 + {/* Mobile hamburger */}
72 + <button
73 + type="button"
74 + aria-label={menuOpen ? "Close menu" : "Open menu"}
75 + aria-expanded={menuOpen}
76 + onClick={() => setMenuOpen((open) => !open)}
77 + className="sm:hidden flex items-center justify-center rounded-md p-1.5 text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-white/10 transition-colors"
78 + >
79 + {menuOpen ? (
80 + /* X icon */
81 + <svg
82 + xmlns="http://www.w3.org/2000/svg"
83 + width="20"
84 + height="20"
85 + viewBox="0 0 24 24"
86 + fill="none"
87 + stroke="currentColor"
88 + strokeWidth="2"
89 + strokeLinecap="round"
90 + strokeLinejoin="round"
91 + aria-hidden="true"
92 + >
93 + <line x1="18" y1="6" x2="6" y2="18" />
94 + <line x1="6" y1="6" x2="18" y2="18" />
95 + </svg>
96 + ) : (
97 + /* Hamburger icon */
98 + <svg
99 + xmlns="http://www.w3.org/2000/svg"
100 + width="20"
101 + height="20"
102 + viewBox="0 0 24 24"
103 + fill="none"
104 + stroke="currentColor"
105 + strokeWidth="2"
106 + strokeLinecap="round"
107 + strokeLinejoin="round"
108 + aria-hidden="true"
109 + >
110 + <line x1="3" y1="6" x2="21" y2="6" />
111 + <line x1="3" y1="12" x2="21" y2="12" />
112 + <line x1="3" y1="18" x2="21" y2="18" />
113 + </svg>
114 + )}
115 + </button>
116 </div>
117 </div>
118 </div>
119 +
120 + {/* Mobile dropdown menu */}
121 + {menuOpen && (
122 + <div className="sm:hidden border-t border-slate-200/60 dark:border-white/[0.06] bg-white dark:bg-[#0A0E14]">
123 + <nav className="mx-auto max-w-5xl flex flex-col px-6 py-3 gap-0.5">
124 + <Link
125 + href="/"
126 + onClick={() => setMenuOpen(false)}
127 + className="flex items-center rounded-md px-3 py-2.5 text-sm font-medium text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-white/10 hover:text-slate-900 dark:hover:text-white transition-colors"
128 + >
129 + Home
130 + </Link>
131 + <Link
132 + href="/services"
133 + onClick={() => setMenuOpen(false)}
134 + className="flex items-center rounded-md px-3 py-2.5 text-sm font-medium text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-white/10 hover:text-slate-900 dark:hover:text-white transition-colors"
135 + >
136 + Services
137 + </Link>
138 + <div className="pt-2 pb-1">
139 + <Link
140 + href="https://console.smbcloud.xyz"
141 + target="_blank"
142 + rel="noopener noreferrer"
143 + onClick={() => setMenuOpen(false)}
144 + className="flex items-center justify-center rounded-md bg-slate-900 dark:bg-white px-4 py-2.5 text-sm font-semibold text-white dark:text-slate-900 hover:bg-slate-700 dark:hover:bg-slate-100 transition-colors"
145 + >
146 + Create account →
147 + </Link>
148 + </div>
149 + </nav>
150 + </div>
151 + )}
152 </header>
153 );
154 }
components/word-cycler.tsx new
+101
@@ -0,0 +1,101 @@
1 +"use client";
2 +
3 +import { useEffect, useMemo, useState } from "react";
4 +
5 +const INTERVAL = 2200;
6 +const DURATION = 340;
7 +
8 +interface WordCyclerProps {
9 + words: string[];
10 + className?: string;
11 + offset?: number; // delay before starting, so two cyclers can sync
12 +}
13 +
14 +export function WordCycler({ words, className, offset = 0 }: WordCyclerProps) {
15 + const shuffled = useMemo(() => {
16 + const arr = [...words];
17 + for (let i = arr.length - 1; i > 0; i--) {
18 + const j = Math.floor(Math.random() * (i + 1));
19 + [arr[i], arr[j]] = [arr[j], arr[i]];
20 + }
21 + return arr;
22 + }, [words]);
23 +
24 + const [index, setIndex] = useState(0);
25 + const [phase, setPhase] = useState<"visible" | "exit" | "enter">("visible");
26 +
27 + useEffect(() => {
28 + const start = setTimeout(() => {
29 + const timer = setInterval(() => {
30 + setPhase("exit");
31 +
32 + setTimeout(() => {
33 + setIndex((i) => (i + 1) % shuffled.length);
34 + setPhase("enter");
35 +
36 + requestAnimationFrame(() => {
37 + requestAnimationFrame(() => {
38 + setPhase("visible");
39 + });
40 + });
41 + }, DURATION);
42 + }, INTERVAL);
43 +
44 + return () => clearInterval(timer);
45 + }, offset);
46 +
47 + return () => clearTimeout(start);
48 + }, [words, offset]);
49 +
50 + return (
51 + <span
52 + style={{
53 + display: "inline-block",
54 + overflow: "hidden",
55 + verticalAlign: "bottom",
56 + lineHeight: "inherit",
57 + }}
58 + >
59 + <span
60 + className={className}
61 + style={{
62 + display: "inline-block",
63 + transition:
64 + phase === "enter"
65 + ? "none"
66 + : `transform ${DURATION}ms cubic-bezier(0.4,0,0.2,1), opacity ${DURATION}ms ease`,
67 + transform:
68 + phase === "visible"
69 + ? "translateY(0)"
70 + : phase === "exit"
71 + ? "translateY(-110%)"
72 + : "translateY(110%)",
73 + opacity: phase === "visible" ? 1 : 0,
74 + }}
75 + >
76 + {shuffled[index]}
77 + </span>
78 + </span>
79 + );
80 +}
81 +
82 +const SERVICE_WORDS = ["Deploy", "Auth", "Mail", "GresIQ"];
83 +const FRAMEWORK_WORDS = ["Rust", "Tauri", "Next.js", "Ruby on Rails", "Vapor"];
84 +
85 +export function ServiceWordCycler() {
86 + return (
87 + <WordCycler
88 + words={SERVICE_WORDS}
89 + className="text-indigo-600 dark:text-indigo-400"
90 + />
91 + );
92 +}
93 +
94 +export function FrameworkWordCycler() {
95 + return (
96 + <WordCycler
97 + words={FRAMEWORK_WORDS}
98 + className="text-indigo-600 dark:text-indigo-400"
99 + />
100 + );
101 +}
next-env.d.ts
+1 -1
@@ -1,6 +1,6 @@
1 /// <reference types="next" />
2 /// <reference types="next/image-types/global" />
3 -import "./.next/types/routes.d.ts";
3 +import "./.next/dev/types/routes.d.ts";
4
5 // NOTE: This file should not be edited
6 // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.