<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: PingClass</title>
    <description>The latest articles on DEV Community by PingClass (@pingclass_off).</description>
    <link>https://dev.to/pingclass_off</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4136030%2F81068b0d-eeee-4243-afdf-395dbc8c411d.png</url>
      <title>DEV Community: PingClass</title>
      <link>https://dev.to/pingclass_off</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pingclass_off"/>
    <language>en</language>
    <item>
      <title>My SaaS runs on static HTML. The build was the easy part.</title>
      <dc:creator>PingClass</dc:creator>
      <pubDate>Thu, 08 Oct 2026 16:20:56 +0000</pubDate>
      <link>https://dev.to/pingclass_off/my-saas-runs-on-static-html-the-build-was-the-easy-part-2p3e</link>
      <guid>https://dev.to/pingclass_off/my-saas-runs-on-static-html-the-build-was-the-easy-part-2p3e</guid>
      <description>&lt;p&gt;&lt;strong&gt;Honestly,&lt;/strong&gt;&lt;br&gt;
When I tell people PingClass has no frontend framework — no React, no build step, just &lt;code&gt;.html&lt;/code&gt; files — the first question is always about the stack. "Doesn't that get unmanageable?"&lt;/p&gt;

&lt;p&gt;Honestly? The rendering layer has never been the hard part. I launched on static HTML plus Supabase, and then real tuition centers started using it. Everything that got hard was underneath: money, automation, and the database boundary.&lt;/p&gt;

&lt;p&gt;This is what shipping a no-framework SaaS actually looked like &lt;em&gt;after&lt;/em&gt; launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack that never changed
&lt;/h2&gt;

&lt;p&gt;Still static HTML + vanilla JS on Vercel, still one Supabase project (Postgres + Auth), still a handful of serverless functions for the dynamic bits. In months of adding payments, reminders, notifications and scheduled jobs, I have not once thought "this needs a framework." Every page is a real &lt;code&gt;.html&lt;/code&gt; file, so Googlebot, ClaudeBot and PerplexityBot read it natively — that part was solved on day one and stayed solved.&lt;/p&gt;

&lt;p&gt;The interesting work was the plumbing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering: solved, don't touch it
&lt;/h2&gt;

&lt;p&gt;Briefly, because it's the least interesting now: marketing pages are static, dashboards are static pages that talk to the database directly, and dynamic content like approved reviews is &lt;em&gt;also&lt;/em&gt; rendered statically on the homepage so crawlers see it. Scripting then skips what's already in the HTML. Rating data is baked in as JSON-LD, &lt;code&gt;llms.txt&lt;/code&gt; tells AI crawlers what's what, and IndexNow pings Bing on every deploy.&lt;/p&gt;

&lt;p&gt;None of that has needed maintenance. It just works, which is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Money is a state machine, not a button
&lt;/h2&gt;

&lt;p&gt;The moment I added payments, the naive version stopped being okay. A "Pay" button is easy. What's hard is that money-as-a-state can be &lt;em&gt;wrong&lt;/em&gt; in ways a normal bug can't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Orders are created server-side, never on the client, so the amount can't be tampered with.&lt;/li&gt;
&lt;li&gt;The success callback is verified server-side against a signature — the browser claiming "paid" means nothing.&lt;/li&gt;
&lt;li&gt;Payment is idempotent: the same event arriving twice must not create two subscriptions.&lt;/li&gt;
&lt;li&gt;Every state change is recorded so it can be reconciled later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I stopped thinking of "subscribe" as an endpoint and started treating it as a state machine with a verified transition. That mindset shift mattered more than any library.&lt;/p&gt;

&lt;h2&gt;
  
  
  The automation is what makes it feel like a product
&lt;/h2&gt;

&lt;p&gt;A dashboard alone is just a nicer spreadsheet. What actually earned the subscription was the stuff owners never have to remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dues auto-generate per student × batch for the current period, with a duplicate guard and a nightly scheduled job.&lt;/li&gt;
&lt;li&gt;Payment reminders, escalation, and receipts go out by email and push.&lt;/li&gt;
&lt;li&gt;Attendance produces absence alerts and a daily reminder, with dedupe so nobody gets pinged twice.&lt;/li&gt;
&lt;li&gt;Plan-expiry notices, invites, and announcements all fire themselves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of it is scheduled functions and managed Postgres jobs. No queue, no worker fleet, no scheduler SaaS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error reporting without a vendor
&lt;/h2&gt;

&lt;p&gt;I didn't want to add a third-party error tracker as yet another dependency, so I built the small version I needed. Every page loads a tiny script that prints a light developer greeting and captures uncaught errors and unhandled rejections. Those reports go to my own endpoint, which merges duplicate crashes so I see distinct problems instead of a flood.&lt;/p&gt;

&lt;p&gt;Little, dependency-free, and good enough — which is the recurring theme.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security lives in the live database, not your migrations
&lt;/h2&gt;

&lt;p&gt;The scariest issue I've dealt with wasn't in my application code. It was in database permissions.&lt;/p&gt;

&lt;p&gt;Here's the part I wish someone had told me earlier: &lt;strong&gt;the effective privileges in a system live in the live database, not in your migration files.&lt;/strong&gt; Your history can look perfectly locked-down while the actual, running database has drifted away from it. A revoke you rely on can be quietly undone by some later, unrelated change — and staring at &lt;code&gt;git log&lt;/code&gt; will never reveal it.&lt;/p&gt;

&lt;p&gt;So I stopped reasoning from the files and started asking the database itself. Now, after any change that touches permissions, I verify against the live system directly: which objects are reachable by which roles, and whether that matches intent. If the live answer disagrees with the files, the live answer wins.&lt;/p&gt;

&lt;p&gt;That one principle has done more for my sleep than any framework ever did, and it costs nothing but discipline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backups, or you don't have a business
&lt;/h2&gt;

&lt;p&gt;Once institutes keep attendance and fee records in your system, "did the backup run?" becomes existential. So snapshots run automatically, and something alerts me if one didn't. There's a retention policy and self-serve account deletion to go with it.&lt;/p&gt;

&lt;p&gt;Backups you have to remember to check aren't backups. They're a liability with extra steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I deliberately did &lt;em&gt;not&lt;/em&gt; add
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A frontend framework.&lt;/strong&gt; The static pages have had no build-step problems because there's no build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A queue / worker system.&lt;/strong&gt; Scheduled functions and managed Postgres jobs cover every background task I have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A third-party error tracker.&lt;/strong&gt; The small in-house version does exactly what I need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A separate "app."&lt;/strong&gt; It's a PWA with a service worker and push notifications. Close enough for now.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The meta-lesson
&lt;/h2&gt;

&lt;p&gt;Spend your complexity budget on the database boundary — permissions, payments, backups — and keep the rendering layer boring. Static HTML plus a managed Postgres plus a CDN has carried this further than I expected, at near-zero cost, and it made the "will people find it?" problem trivial because every page is HTML that search engines and LLMs read natively.&lt;/p&gt;

&lt;p&gt;The framework was never going to be what made this work. The unglamorous stuff did.&lt;/p&gt;

&lt;p&gt;PingClass is live — free up to 20 students, then ₹249/mo and ₹599/mo, also on Product Hunt and G2. Happy to go deep on the money state machine, the static-HTML approach, or the tuition-center niche.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>database</category>
      <category>saas</category>
      <category>supabase</category>
    </item>
    <item>
      <title>I built a tuition management SaaS with plain HTML, Supabase and Vercel</title>
      <dc:creator>PingClass</dc:creator>
      <pubDate>Mon, 21 Sep 2026 15:50:44 +0000</pubDate>
      <link>https://dev.to/pingclass_off/i-built-a-tuition-management-saas-with-plain-html-supabase-and-vercel-4o91</link>
      <guid>https://dev.to/pingclass_off/i-built-a-tuition-management-saas-with-plain-html-supabase-and-vercel-4o91</guid>
      <description>&lt;p&gt;I spent a few weeks building PingClass — institute-management software for small tuition and coaching centers in India (1–5 teachers, under ~200 students). The kind of businesses that still run on a patchwork of Google Sheets, paper registers, and four WhatsApp groups.&lt;/p&gt;

&lt;p&gt;The interesting part isn't the product though, it's how it's built: no framework, no frontend build step, static HTML files, a Postgres database, and a free CDN. I want to share the stack and what I learned, because this is the least-discussed way to ship a SaaS that runs and ranks.&lt;/p&gt;

&lt;p&gt;The constraints that pushed me here&lt;br&gt;
I'm a solo maker shipping in evenings. I didn't want a toolchain I have to babysit.&lt;br&gt;
The customers are offline-first small business owners. They open a link, it should just work, load fast, and not break.&lt;br&gt;
I wanted the site's marketing pages to be instantly crawlable by Google, Bing, and the AI search bots — static HTML is the most undisputed, indexable thing that exists.&lt;br&gt;
The stack&lt;br&gt;
Static HTML + vanilla JS   -&amp;gt;  Vercel (free tier, global CDN)&lt;br&gt;
Supabase (Postgres + Auth) -&amp;gt;  tenant data, users, reviews&lt;br&gt;
JSON-LD structured data    -&amp;gt;  SEO / AI answer engines&lt;br&gt;
llms.txt                   -&amp;gt;  explicit signal for AI crawlers&lt;br&gt;
IndexNow                   -&amp;gt;  instant Bing re-crawl on deploy&lt;br&gt;
Why static HTML&lt;br&gt;
Every page is a real .html file. There's no hydration, no #app shell, no client routing. Googlebot, ClaudeBot, PerplexityBot and GPTBot all just read the HTML — headings, text, structured data — directly. The marketing site lives in the most defensible form SEO can have.&lt;/p&gt;

&lt;p&gt;The interactive parts (auth, dashboards) are separate static pages that talk to Supabase with client-side JS.&lt;/p&gt;

&lt;p&gt;Supabase for everything data&lt;br&gt;
A single Supabase project handles:&lt;/p&gt;

&lt;p&gt;Postgres — core tables: students, batches, attendance, fees, announcements, parents.&lt;br&gt;
Auth — email-based sign-in with admin/teacher/parent roles.&lt;br&gt;
RLS policies — row-level security so an institute only ever sees its own rows, enforced at the database level.&lt;br&gt;
The fee module auto-generates monthly dues per student × batch and lets owners send due reminders — that's where the "fewer spreadsheets" pitch actually lands.&lt;/p&gt;

&lt;p&gt;One query the site runs for its public reviews section:&lt;/p&gt;

&lt;p&gt;const { data: reviews } = await supabase&lt;br&gt;
  .from('reviews')&lt;br&gt;
  .select('author_name, quote, rating, institute')&lt;br&gt;
  .eq('status', 'approved')&lt;br&gt;
The tricky bit: reviews are loaded client-side, so they'd be invisible to crawlers. I solved it by rendering the approved reviews as static HTML on the homepage too, and having JS skip authors that are already statically present. Same for the aggregateRating structured data — crawlers get the facts front-and-center.&lt;/p&gt;

&lt;p&gt;What I'd do differently (maybe)&lt;br&gt;
Payment gateway. Ledgers and reminders work; actual collection is still manual. A Razorpay integration is the next obvious wedge.&lt;br&gt;
Mobile app. Parents want an app. Web-first was the right place to start, but "Is there an app?" is the #1 question.&lt;br&gt;
Validate before building as much. The fee/attendance/parent-communication triangle seems right, but I'd have liked real signed-up centers earlier rather than a polished product first.&lt;br&gt;
The meta-lesson&lt;br&gt;
You don't need React + a queue system + Kubernetes for a tool that two dozen tuition centers might use. Static HTML + a managed Postgres + a CDN handled everything, cost almost nothing, and made the "will people find it?" problem dramatically easier — because every page is HTML that search engines and LLMs read natively.&lt;/p&gt;

&lt;p&gt;If you're building a small vertical SaaS and feel the pull toward a heavier stack, ask whether the extra machinery earns its keep.&lt;/p&gt;

&lt;p&gt;PingClass is live — free tier up to 20 students, then ₹249/mo and ₹599/mo. As also featured on Product Hunt and G2. Happy to answer questions about the stack or the tuition-center niche.&lt;/p&gt;

</description>
      <category>database</category>
      <category>saas</category>
      <category>startup</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
