This is great - thanks for sharing. I am actually building something very similar myself as I started building a couple SaaS and though it would be nice to extract the common pieces in a template.
My stack is similar, with a few differences:
- Go backend with sqlc, but using ConnectRPC[1]. I chose this as it allows me to define a proper API scheme and generate a decent-quality Typescript client.
- Nuxt (Vue) instead of Next.js (React). I chose this even though I'm new to vue cause I saw the open source components and templates here [2] (especially the dashboard template: [3]) and was convinced.
I'll definitely check out your repo as inspiration.
ConnectRPC looks interesting actually, proper API schema with generated TS client is nice. And that Nuxt dashboard template is clean, hadnt seen that before.
If you spot anything in the repo or have ideas, feel free to open a PR. Or just reach out directly if you wanna chat about the stack. Always down to learn from someone building similar stuff
This is cool - Whenever I have a new idea for a thing I spend too much time writing boilerplate IAM and backend stuff, taking away time that could be spend on actual business logic. Thought about packaging the boilerplate stuff up before, never gotten around to it. Glad you did!
A thing to consider would be to make it easier (or perhaps bake it in) to separate out parts of the app into a separate origin. Something that would be good for pretty much any SaaS app would be to separate the IAM out (could still embed it with an iframe) - this allows you to keep a fairly tight security policy for the IAM stuff and a more lax one for the rest of the app. Kinda how Google separates out accounts.google.com.
Thanks! That's exactly why I open-sourced it. Instead of this living in my private repo getting occasional updates, now the community can push it forward. Improvements flow back to everyone, including me. Win-win.
Your IAM separation idea is interesting. Separate origin for auth would tighten the CSP significantly. The backend is already modular, so spinning the auth service into its own container with a stricter policy is doable. Worth exploring. Would you mind opening an issue on the repo so I don't lose track of this?
A general question for the room: where's the tipping point where you need a "proper" backend, in a different language, with all the inconveniences of possible type safety issues and impedance mismatches?
Because I feel like for 90% of small-medium projects it's just good enough with all the backend stuff within the same Next.js process as the front-end. I just do "separation of concerns"-ish with the code organization and funnel all communication with something structured and type safe like tRPC.
Feels separate enough but very pleasant to work anyway.
For most CRUD apps, Next.js + tRPC is the right call.
My tipping point was long-running tasks (OCR, AI processing that takes 30+ seconds) and wanting to scale backend compute separately from frontend serving.
If you don't have those needs, stick with what you have.
Thanks for the answer! I've hit those tipping points myself in exactly the same scenarios (OCR and AI). For me, ends up being hacky or just decoupled (independent job runners). Makes sense to have a proper monolith backend for these.
Next is an ok choice (IMO), but there are definitely some things Next does that you want to be aware of up front.
* It wants to be your back-end. If you have a separate back-end, get ready to write back-end auth code twice, and probably in 2 different languages, and some brittle proxy code that will break the next time the Next guys decide they want to change how middleware works (again).
* The maintainers aren't particularly helpful. Having built a couple sites using Next, many of our questions ended up being answered with some variation of, "You're holding it wrong," but it was clear they just didn't want to support our (and other users' submitting issues) scenarios.
* Whether you are on Vercel or not, the team behind Next is very motivated to get you onto Vercel. You can expect their technical choices to go more towards that path. This is at odds with the goals of this project. Coupled with the above, expect to have little to no agency to raise issues and have them solved beyond simple/obvious bug fixes, even after you've invested your project into their platform.
* Next really struggles in situations where your users are your customers' customers, and your customers want something more white-labeled. As soon as this bleeds into the arena of using custom domains per customer and such, some of the advantages of Next start to become disadvantages.
Many of the pieces Next offers are sort of optional, but if you don't fit their idea of how a piece (such as auth via next-auth or their take on server-side components) should work, you're left to solve on your own. It's not the end of the world to have to implement your own auth flow with oidc-client, but it can be a little risky and my brain doesn't hold onto OIDC or OAuth2 so every time I implement an auth flow from scratch I end up having to look up how it should work.
That said, if you end up having to deal with more than a couple of the above things, Next moves from an ok choice for the project to a poor choice.
This is really helpful context, thanks for writing it out.
You’re right that Next wants to be your backend and thats exactly why I kept the real backend separate in Go.
The Go backend handles all auth, billing, database, everything.
Next is just a frontend that calls the API. So if Next keeps changing things or pushes too hard toward Vercel, you swap it out. The backend doesnt care.
The white-label / custom domain point is interesting, hadnt thought about that edge case. Good to keep in mind.
Honestly the backend is the important part here. The frontend is just one way to consume it
One question though:
What made you avoid lock-in via platforms like supabase but then choose to be locked in on the AuthN/Z side with a proprietary solution?
Totally valid. The Go backend is just a REST API with no Next.js coupling. You could swap the frontend for Go templates + htmx without changing the backend at all.
And yeah, Polar.sh has been great. Merchant of Record means I don't think about tax compliance.
Nice project and great idea and a reasonable selection of technologies that optimize for low cost deployment.
However, my biggest concern is the glaringly lack of comprehensive tests whatsoever. I have to even question if this project is production ready at all.
Until that is in place, I really do not think this is "production" quality I'm afraid.
My stack is similar, with a few differences:
- Go backend with sqlc, but using ConnectRPC[1]. I chose this as it allows me to define a proper API scheme and generate a decent-quality Typescript client.
- Nuxt (Vue) instead of Next.js (React). I chose this even though I'm new to vue cause I saw the open source components and templates here [2] (especially the dashboard template: [3]) and was convinced.
I'll definitely check out your repo as inspiration.
[1]: https://connectrpc.com/
[2]: https://ui.nuxt.com/
[3]: https://dashboard-template.nuxt.dev/
ConnectRPC looks interesting actually, proper API schema with generated TS client is nice. And that Nuxt dashboard template is clean, hadnt seen that before.
If you spot anything in the repo or have ideas, feel free to open a PR. Or just reach out directly if you wanna chat about the stack. Always down to learn from someone building similar stuff
Would love to explore different libraries
A thing to consider would be to make it easier (or perhaps bake it in) to separate out parts of the app into a separate origin. Something that would be good for pretty much any SaaS app would be to separate the IAM out (could still embed it with an iframe) - this allows you to keep a fairly tight security policy for the IAM stuff and a more lax one for the rest of the app. Kinda how Google separates out accounts.google.com.
Your IAM separation idea is interesting. Separate origin for auth would tighten the CSP significantly. The backend is already modular, so spinning the auth service into its own container with a stricter policy is doable. Worth exploring. Would you mind opening an issue on the repo so I don't lose track of this?
I created a simple start kit set of packages for my projects, not as exhaustive as yours though -- https://github.com/krsoninikhil/go-rest-kit
Thanks for your feedback, and I am always open for any questions!
A general question for the room: where's the tipping point where you need a "proper" backend, in a different language, with all the inconveniences of possible type safety issues and impedance mismatches?
Because I feel like for 90% of small-medium projects it's just good enough with all the backend stuff within the same Next.js process as the front-end. I just do "separation of concerns"-ish with the code organization and funnel all communication with something structured and type safe like tRPC.
Feels separate enough but very pleasant to work anyway.
Am I doing it wrong?
For most CRUD apps, Next.js + tRPC is the right call.
My tipping point was long-running tasks (OCR, AI processing that takes 30+ seconds) and wanting to scale backend compute separately from frontend serving.
If you don't have those needs, stick with what you have.
Congrats on the launch again!
* It wants to be your back-end. If you have a separate back-end, get ready to write back-end auth code twice, and probably in 2 different languages, and some brittle proxy code that will break the next time the Next guys decide they want to change how middleware works (again).
* The maintainers aren't particularly helpful. Having built a couple sites using Next, many of our questions ended up being answered with some variation of, "You're holding it wrong," but it was clear they just didn't want to support our (and other users' submitting issues) scenarios.
* Whether you are on Vercel or not, the team behind Next is very motivated to get you onto Vercel. You can expect their technical choices to go more towards that path. This is at odds with the goals of this project. Coupled with the above, expect to have little to no agency to raise issues and have them solved beyond simple/obvious bug fixes, even after you've invested your project into their platform.
* Next really struggles in situations where your users are your customers' customers, and your customers want something more white-labeled. As soon as this bleeds into the arena of using custom domains per customer and such, some of the advantages of Next start to become disadvantages.
Many of the pieces Next offers are sort of optional, but if you don't fit their idea of how a piece (such as auth via next-auth or their take on server-side components) should work, you're left to solve on your own. It's not the end of the world to have to implement your own auth flow with oidc-client, but it can be a little risky and my brain doesn't hold onto OIDC or OAuth2 so every time I implement an auth flow from scratch I end up having to look up how it should work.
That said, if you end up having to deal with more than a couple of the above things, Next moves from an ok choice for the project to a poor choice.
You’re right that Next wants to be your backend and thats exactly why I kept the real backend separate in Go.
The Go backend handles all auth, billing, database, everything.
Next is just a frontend that calls the API. So if Next keeps changing things or pushes too hard toward Vercel, you swap it out. The backend doesnt care.
The white-label / custom domain point is interesting, hadnt thought about that edge case. Good to keep in mind.
Honestly the backend is the important part here. The frontend is just one way to consume it
But the frontend and backend arent tightly coupled at all.
You can swap Next for Vite, Nuxt, whatever you want and connect it to the same Go backend.
Only thing you'd need to copy over is some auth and billing related stuff on the frontend side
One question though: What made you avoid lock-in via platforms like supabase but then choose to be locked in on the AuthN/Z side with a proprietary solution?
Stytch lock-in is shallow (just an API behind a ~200 line adapter).
If I swap Stytch for Ory or Auth0, I rewrite one file. The rest of the app doesn't know the difference.
I would prefer if it had a more leightweight htmx approach, but I guess it would be useful to some people.
I stripped out the business logic and keys, then pushed it as a clean starting point.
The "in production you would" comments are guides for where to add your own config.
Single commit because I didn't want my app's git history in an open source repo.
However, my biggest concern is the glaringly lack of comprehensive tests whatsoever. I have to even question if this project is production ready at all.
Until that is in place, I really do not think this is "production" quality I'm afraid.
PRs welcome if anyone wants to help out
If you need anything don’t hesitate to reach out, you can find my info in the repo