From 2dd8fb5397b1985926396bb26c46bba54780b1b6 Mon Sep 17 00:00:00 2001 From: Gerard Kavanagh Date: Sat, 15 Aug 2026 15:12:09 +0100 Subject: [PATCH] feat(blog): two editorial SEO posts + single source for blog content The platform blog kept its posts in two hardcoded arrays - one in the index, one in the detail page - which had already drifted out of sync. Editorial posts now live in lib/blog/posts.ts and both pages read from it, so a new post is one edit rather than two. Adds two posts written against what the product actually does: - WordPress vs BudStacks, grounded in the four plugins our own WordPress fleet needed (ID upload, direct pay, local pricing, policies) and the fleet-patching problem at scale. Names the cases where WordPress is the better answer. - The real economics of a storefront: margin per gram, where the 20% share goes and when it stops, break-even in grams/day, and an explicit statement that the work is not passive - which matches the guides rather than contradicting them. Both avoid earnings promises; the economics post carries an illustrative-figures disclaimer. The six original sample posts stay inline until each is rewritten. --- nextjs_space/app/blog/[slug]/page.tsx | 16 ++- nextjs_space/app/blog/page.tsx | 11 +- nextjs_space/lib/blog/posts.ts | 145 ++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 nextjs_space/lib/blog/posts.ts diff --git a/nextjs_space/app/blog/[slug]/page.tsx b/nextjs_space/app/blog/[slug]/page.tsx index a39abdb4..7c993e4a 100644 --- a/nextjs_space/app/blog/[slug]/page.tsx +++ b/nextjs_space/app/blog/[slug]/page.tsx @@ -3,9 +3,12 @@ import { notFound } from "next/navigation"; import { FileText, ArrowLeft } from "lucide-react"; import sanitizeHtml from "sanitize-html"; import { Navbar, Footer } from "@/components/landing"; +import { BLOG_POSTS } from "@/lib/blog/posts"; -// Sample blog posts data - in production this would come from a CMS/database -const blogPosts = [ +// Editorial posts live in lib/blog/posts.ts (single source, shared with the +// index). The entries below are the original samples, kept until each is +// rewritten — migrate them into that module as you go. +const samplePosts = [ { id: 1, slug: "getting-started-with-medical-cannabis-franchise", @@ -176,9 +179,12 @@ const blogPosts = [ }, ]; +/** Editorial posts first, then the remaining samples. */ +const allPosts = [...BLOG_POSTS, ...samplePosts]; + // Get related posts (exclude current post) function getRelatedPosts(currentSlug: string, limit = 2) { - return blogPosts + return allPosts .filter((post) => post.slug !== currentSlug) .slice(0, limit); } @@ -189,7 +195,7 @@ interface PageProps { export default async function BlogPostPage({ params }: PageProps) { const { slug } = await params; - const post = blogPosts.find((p) => p.slug === slug); + const post = allPosts.find((p) => p.slug === slug); if (!post) { notFound(); @@ -336,7 +342,7 @@ export default async function BlogPostPage({ params }: PageProps) { // Generate static params for all blog posts export function generateStaticParams() { - return blogPosts.map((post) => ({ + return allPosts.map((post) => ({ slug: post.slug, })); } diff --git a/nextjs_space/app/blog/page.tsx b/nextjs_space/app/blog/page.tsx index 7bad1fd8..a7cd4d29 100644 --- a/nextjs_space/app/blog/page.tsx +++ b/nextjs_space/app/blog/page.tsx @@ -1,11 +1,12 @@ import Link from "next/link"; import { FileText } from "lucide-react"; import { Navbar, Footer } from "@/components/landing"; +import { BLOG_POSTS } from "@/lib/blog/posts"; -// Sample blog posts - in a real app these would come from a CMS or database. -// Images are local SVG placeholders (themed to BudStacks palette) — -// placeholder while the blog widget is being finished. -const blogPosts = [ +// Editorial posts live in lib/blog/posts.ts (single source, shared with the +// detail page). The entries below are the original samples, kept until each is +// rewritten — migrate them into that module as you go. +const samplePosts = [ { id: 1, slug: "getting-started-with-medical-cannabis-franchise", @@ -88,7 +89,7 @@ export default function BlogPage() { {/* Blog grid */}
- {blogPosts.map((post) => ( + {[...BLOG_POSTS, ...samplePosts].map((post) => ( We are not neutral here, and you should read this knowing that. But we also run a fleet of cannabis storefronts on WordPress — real ones, taking real orders — alongside the BudStacks platform. That gives us something most comparison articles do not have: the specific list of things that broke.

+ +

So here is the honest version, including where WordPress wins.

+ +

What WordPress genuinely does well

+

It is the most flexible publishing tool ever built, it costs almost nothing to start, and every developer on earth knows it. If you want a page to look a particular way, someone has already built a plugin for it. For a blog, a brochure site, or an ordinary shop selling ordinary things, WordPress plus WooCommerce is a sensible, boring, correct answer.

+ +

If your storefront is mostly a shop window and your orders arrive by phone, stop reading. WordPress will serve you well and cost you a few pounds a month.

+ +

WordPress does not know it is selling cannabis

+

This is the whole thing, really. WooCommerce was built to sell t-shirts. It has no concept of a patient, a prescription, an identity document, or a jurisdiction that will fine you for shipping into it.

+ +

Every one of those concepts has to be bolted on. In our own WordPress fleet, that meant building and maintaining four separate plugins:

+ + + +

None of those is exotic. All of them are the sort of thing you discover you need three weeks after launch, usually because something went wrong.

+ +

The update problem nobody mentions

+

One WordPress site is a pleasure. Twenty is a job. Two hundred is a full-time engineering function.

+ +

Every plugin you add is a thing that updates on its own schedule, occasionally breaks against a WordPress core release, and needs testing across every site you run. We ended up writing a fleet patching tool purely to push a single fix across hundreds of sites without visiting each one by hand. That tool exists because the alternative was a person clicking through admin panels for two days.

+ +

If you plan to run one store, this is not your problem. If you plan to run several, it is the problem.

+ +

Compliance is not a plugin

+

The parts of a medical cannabis store that keep you out of trouble are unglamorous and structural: identity verification before an order can be placed, a consultation questionnaire, geographic gating for regions you are not licensed to serve, consent capture that survives an audit, and a log of who changed what and when that cannot be quietly edited afterwards.

+ +

You can assemble all of that on WordPress. People do. But you are then the person responsible for it — for the encryption of those ID documents, for the redaction of personal data in your logs, for proving to a regulator that the audit trail is genuine. That responsibility does not go away because a plugin author said it was handled.

+ +

On BudStacks those pieces are part of the platform rather than an add-on, which is a claim worth being precise about: it does not make you compliant. It means the mechanism exists and works the same way on every store, and that keeping it working is our job rather than yours. You still have to operate lawfully in your own jurisdiction.

+ +

The honest cost comparison

+

WordPress hosting runs a few pounds a month. That is genuinely cheaper than any SaaS subscription, and anyone who tells you otherwise is selling something.

+ +

The cost you are actually comparing is the build. Assembling a compliant medical cannabis storefront from scratch — the identity workflows, the payment integration, the supplier connection, the legal review — is the part that runs into serious money and serious time. Our own estimate for doing it properly from nothing is $200,000 to $500,000 and one to three years, which matches what operators who have tried it tell us.

+ +

The subscription is not competing with your hosting bill. It is competing with that build.

+ +

So which one

+

Choose WordPress if you have development capacity in-house, you are running one or two sites, you want total control of every pixel and every integration, and you are comfortable owning the compliance surface yourself.

+ +

Choose BudStacks if you want to be selling this quarter rather than building this year, if you would rather spend your time on patients than on plugin updates, and if you want the compliance machinery to be someone else's problem to maintain.

+ +

There is no clever answer here. It is a straight trade: control and cheap hosting on one side, time and maintained compliance on the other. Anyone who tells you one option is simply better has not run both.

+ `, + }, + { + id: 102, + slug: "real-economics-medical-cannabis-storefront", + title: "The Real Economics of a Medical Cannabis Storefront", + date: "Aug 15, 2026", + author: "BudStacks", + role: "Platform Team", + excerpt: + "Margin per gram, where the profit share goes, what the overhead really is, and the number that decides whether any of it works.", + image: "/images/blog/post-06-analytics.svg", + content: ` +

Most people considering a cannabis storefront ask about the setup cost first. It is the wrong first question. The setup cost is a one-off you can plan for. The economics are what you live in every month afterwards.

+ +

So let us do the arithmetic properly, including the parts that are less fun.

+ +

Start with one gram

+

The unit that matters is a single gram, and the number that matters is your margin on it — roughly $3 to $4 depending on what you are selling and what you charge.

+ +

That figure is the whole business in miniature. Everything below is that number multiplied by how much you sell and reduced by what it costs you to operate.

+ +

Where the profit share goes, and when it stops

+

On the Lease-to-Own path, 20% of gross goes to the platform during the lease. On a $4 margin, that is $0.80 to us and $3.20 to you.

+ +

The part worth understanding is that this is temporary by design. Complete the lease and the profit share drops to zero, along with the monthly licence rental. Buy the licence outright at the start and it never applies at all. Those are genuinely different financial shapes: the lease trades a slice of your upside for a low entry cost, and outright ownership trades capital up front for keeping everything.

+ +

Which suits you depends entirely on whether capital or certainty is the thing you are short of.

+ +

The overhead, stated plainly

+

During a lease you are paying the $169 monthly licence rental plus your subscription tier — $99, $149, or $199 depending on which tools you need. Worst case, on the top tier, that is $368 a month. On the entry tier it is $268.

+ +

Call it somewhere between three and four and a half thousand dollars a year while you are leasing. That is your floor. You need to clear it before you have made a penny.

+ +

At a $3.50 margin, covering $368 of monthly overhead takes about 105 grams a month. Roughly three and a half grams a day. That is the actual break-even, and it is a more useful number than any revenue projection.

+ +

The number that decides everything

+

Here is the part the brochures skip. Your revenue is not driven by the platform, the design, or the product range. It is driven by two things: how many patients you have, and how often they come back.

+ +

A patient buying 30 grams a month at a $4 margin is worth about $120 of margin a month to you. Ten of them is $1,200. A hundred is $12,000. The arithmetic is not complicated — but every one of those patients has to be found, verified, and kept.

+ +

Reorder rate is the quiet multiplier. A store where patients return every month is a fundamentally different business from one where they buy once and vanish, even with identical patient counts. This is why the analytics in the platform track your reorder cycle and flag patients who are overdue: that list is worth more than any acquisition campaign, because winning back someone who already trusts you is the cheapest sale you will ever make.

+ +

The part that is not passive

+

You will see this sold as passive income. It is not, and we would rather tell you now than have you discover it in month two.

+ +

What you do not have to do is real and substantial: you never handle stock, you never touch the product, fulfilment and logistics are handled, and the compliance machinery runs without you. That removes most of what makes traditional retail exhausting.

+ +

What remains is a job. Orders arrive and need attention. Identity documents need reviewing and approving, and rejecting one badly loses you a customer. Patients email with questions. Somebody has to write the content that brings new people in, and somebody has to notice when reorders slow down. Our own store owners describe it as a couple of focused hours a day once it is running, more at the start.

+ +

That is a good business. It is not a passive one, and anyone describing it as passive is either misinformed or selling to you.

+ +

Do the arithmetic yourself

+

Take your realistic patient count for month six — not month sixty. Multiply by the grams you expect each to buy. Multiply by your margin. Subtract your overhead. That number, not anyone's projection table, is what you should make the decision on.

+ +

If it works at a conservative patient count, the upside takes care of itself. If it only works at a heroic one, you have learned something valuable before spending anything.

+ +

Every figure here is illustrative. Actual results depend on your market, your pricing, your patient base, and your own effort. Nothing on this page is a guarantee of earnings, and nothing here is financial advice.

+ `, + }, +];