Back to blogs

From Starter to Product

Turn the standalone Next.js starter into your product without redesigning the Auth, billing, data, and server boundaries already wired into the app.

By Antoine RossPublished
Share:

Turning a starter into your own product creates an awkward question almost immediately: how much of the starter are you supposed to replace?

The obvious parts are easy. The product name, visual identity, landing page, example content, and customer-facing copy exist so that you can change them. The harder decision begins when customization reaches Auth, billing, database access, webhooks, and server-side provider code. Those pieces may still look like someone else's code, but unlike the demo copy, they already solve problems your product will need.

Rewriting them can make the repository feel more "yours" while quietly giving back one of the main advantages of starting from a template: you began with working boundaries instead of having to design them yourself.

That does not mean the foundations should never change. It means different changes deserve different levels of intervention. Before replacing something, it helps to know whether you are changing the product, configuring an existing system, extending it, or replacing one of its contracts.

Make the product yours without rebuilding every foundation first.

Decide how deep the change goes

A useful way to reason about customization is to separate changes into four levels.

ChangeWhat it meansQuestion to ask
ReplaceThe starter supplied an example that should become yours.What should visibly belong to my product?
ConfigureThe existing system fits, but it needs your values or provider state.Where does this value already have an owner?
ExtendThe structure works, but your product needs new behavior inside it.Where does this feature already have a place to live?
RebuildYour product requirement no longer fits the existing contract.What can the current foundation not support?

The distinction is useful because the cost and risk increase as you move inward.

Replacing a logo does not change how users authenticate. Changing a Stripe Price ID reaches further because Checkout depends on that configuration. Adding a product feature changes application behavior, but it can still use the same application boundaries that Auth and billing already use. Replacing Auth changes an identity model that other parts of the product depend on.

None of those changes is inherently wrong. They are simply different kinds of work, and they should not all begin with the same instinct to rewrite.

Most early customization belongs in Replace, Configure, and Extend. A Rebuild should begin with a product requirement that the current foundation cannot satisfy.

Go deeper only when the product requirement goes deeper.

Replace what should become yours

The public product should stop looking like a starter quickly.

The product name, visual identity, landing page, navigation, example content, screenshots, pricing presentation, and customer-facing copy all describe your business. A template cannot make those decisions for you, and there is little value in preserving them just because they shipped with the repository.

This is also where you can make broad changes without changing the responsibilities that Auth, billing, and persistence already own. You can make the application visibly yours without first changing how users authenticate, how Checkout reaches Stripe, or how paid access is stored.

The useful boundary is not between "template code" and "your code." It is between product identity and system responsibility.

Email is a simple example. Your Auth emails and billing follow-up emails should look and sound like the same product, but that does not mean they should become one system. Supabase Auth still owns account messages. Application billing flows still own their own follow-up. The visual identity can converge without the responsibilities collapsing.

Branding follows the same rule. Public demo branding is meant to be replaced. Repository license and ownership notices that are intended to remain are a different concern.

The general pattern is simple: replace aggressively where the starter is expressing someone else's product, and be more deliberate where it is expressing a working system boundary.

Configure before you rebuild

Once the visible product is yours, the next risk is mistaking configuration for architecture.

Moving from a local or demo application to yourproduct.com is one product change, but it crosses several configuration owners. The Next.js application needs its public origin, Supabase Auth needs the correct callback and redirect destinations, and Stripe needs the deployed webhook destination used by the billing flow.

Those settings describe the same product, but they are not the same kind of state.

One product change can affect several owners without creating a new owner.

Trying to centralize all of that into a new application configuration layer can feel cleaner, but it often creates duplicate authorities instead. Now the app says one thing, the provider says another, and the failure appears somewhere between them.

The better rule is to keep each fact with the system that already owns it.

If pricing already comes from pricing configuration, change it there instead of copying the same amount into a card, Checkout code, and another configuration file. If Supabase owns the allowed Auth redirects, configure Supabase instead of inventing a second redirect authority inside the app. If a credential belongs on the server, keep it there rather than making it browser-visible because that seems easier to customize.

The exact values and setup steps belong in the Docs. Configuration covers which values are browser-safe, which stay server-only, and which come from providers.

The product decision is more important than the commands: configure the system you already have before deciding that the system itself needs replacement.

Keep the boundaries that make changes safer

The standalone template deliberately keeps deployment simple: one Next.js application.

That simplicity is useful only if "one app" does not become "everything can call everything."

At a high level, the application already separates browser behavior from privileged server work and provider or database access:

You do not need to memorize every layer to benefit from this structure. What matters is the class of mistakes it prevents.

A dashboard component does not need your Stripe secret just because it displays billing state. A page does not need privileged database credentials because it renders protected data. Changing the pricing UI does not need to create a new source of paid-access truth. An admin credential does not become a substitute for checking which user made the request simply because it can read more data.

One application can stay simple without turning every layer into the same trust boundary.

There is a cost to keeping those boundaries. A small feature may touch more than one file. A provider call placed directly beside the button that uses it can look shorter in the moment.

The tradeoff is that the component now needs to understand credentials, provider behavior, error handling, or persistence concerns that belong elsewhere. The shortcut saves a little structure now by moving responsibility into the wrong place.

The existing application gives that responsibility somewhere to go already.

If you want the deeper implementation model, Project Structure shows how the standalone app separates browser calls, Route Handlers, server-only services, and persistence. Authentication shows how the Supabase session and persisted billing state come together when the application decides whether a user has access.

The point of those boundaries is not architectural neatness. It is that adding something new should not require redesigning how the rest of the application handles trust.

Add the product, not another architecture

This is the point where a starter starts paying for itself.

Once the visible identity and provider configuration are yours, you can spend time on the feature that makes your product different instead of rebuilding infrastructure that already has a place.

A first feature does not automatically need another backend because it introduces a new object. It does not need a new data-access pattern because the existing one feels generic. It does not need a new abstraction before there is a second real problem for that abstraction to solve.

Start by putting the feature through the application path that already exists:

That is intentionally less detailed than the full implementation path. The important point here is that there is already somewhere for the feature to live.

If the feature later exposes a real limitation, that limitation gives you evidence for a deeper change. Perhaps the current access model cannot represent organizations. Perhaps the billing model cannot express usage. Perhaps a separate service becomes necessary because another client must consume the same API independently.

Those are useful architectural signals because they come from the product.

Trying to predict the final architecture before the first feature exists is different. It usually means solving for imagined scale or taste rather than a requirement the customer can name.

The next article, Build Your First Feature, goes deeper into the complete standalone Next.js feature path once that article is available.

For now, the useful rule is to add the product before adding another architecture.

Rebuild when the contract stops fitting

Preserving a foundation does not mean preserving it forever.

The existing design is useful only while it still represents what the product needs.

Organization-scoped access may require a different entitlement model. Usage-based billing may change what billing state means. A customer requirement may force another identity provider. A sufficiently different data model may change where persistence responsibilities belong.

Those are legitimate reasons to rebuild because the product has created a new constraint.

Compare that with reasons such as "Supabase feels too simple," "a serious application should have a separate backend," or "I would have written this differently." Each may eventually lead to a useful discussion, but none of them explains what the current system prevents the product from doing.

Before replacing a foundation, answer three questions:

  1. What does the product now require?
  2. Why can the existing foundation not support it?
  3. What responsibility will the replacement own?

If those answers are clear, a Rebuild may be exactly the right change.

If they are not, inherited code is not a defect merely because it is inherited. The starter has already given the product a working place to begin and defined places to extend.

Change the next layer the product actually needs.

About the author

I'm Antoine. I build CoreMVP and write about the problems I run into, the decisions I make, and what I learn along the way.