Blocks vs Components: What Survives a Rebrand
A practical guide to ShipAny Next blocks and components: what to rewrite, what to keep, and how the split keeps SaaS customization clean.
Every SaaS template promises easy customization. The hard part is not changing the first headline. The hard part is changing the product, brand, pricing story, navigation, and landing page structure without breaking the reusable parts underneath.
ShipAny Next draws a strict line between two kinds of frontend files:
A file that reads translations is a block. A file that takes all content via props is a component.
That rule is small, but it decides what survives a rebrand.
The problem this split solves
A template usually starts as someone else's product story. It has their hero copy, their feature order, their screenshots, their pricing framing, and their default navigation. If those choices are mixed into the same files as durable UI primitives, every new project turns into a cleanup job.
You end up asking the same questions:
- Is this file product copy or reusable UI?
- Can I delete this section safely?
- Why does a pricing card know about the landing page locale?
- Why does a header component know which links this app uses?
- Will changing this demo block break the dashboard or admin UI?
The blocks/components split keeps those answers boring.
Blocks are allowed to know the current product. Components are not.
Blocks are product wiring
Blocks live in src/blocks/. They are zero-config page sections such as <Header />, <Hero />, <Pricing />, <Blog />, and <Footer />.
A block may read translations, choose copy, assemble navigation links, pick icons, decide which pricing plans to show, and pass the final content into a durable component.
This is a real block-shaped pattern from the app:
import { m } from '@/paraglide/messages.js';
import { SiteHeader } from '@/components/site-header';
export function Header() {
const navLinks = [
{ href: '/', label: m['landing.nav.generator']() },
{ href: '/#features', label: m['landing.nav.use_cases']() },
{ href: '/pricing', label: m['landing.nav.pricing']() },
{ href: '/blog', label: m['landing.nav.blog']() },
];
return <SiteHeader navLinks={navLinks} />;
}
This file knows the landing page's labels. It knows which links matter for this product. That is fine because it is a block.
When you build a new SaaS on top of the engine, this is exactly the kind of file you should expect to rewrite.
Components are reusable UI
Components live in src/components/. They should not read product copy from translation files. They receive content as props and render it.
SiteHeader is a component. It does not decide what links exist. It only renders the navLinks it receives.
That makes the component useful across products:
- a podcast tools site can pass generator, pricing, and blog links
- a lead magnet tool can pass validation, examples, and billing links
- an internal dashboard can use a different app shell entirely
- a future rebrand can replace the whole header block without rewriting the header primitive
The same idea applies to PricingTable, BlogCard, AppSidebar, form fields, data tables, dialogs, buttons, and other primitives. They are not where your landing page story should live.
The file ownership rule
Use this rule before editing a page:
| Question | If yes, it belongs in |
|---|---|
Does it read m['...']() or other product text? | src/blocks/ |
| Does it choose section order, nav links, feature lists, or CTA copy? | src/blocks/ or the route |
| Does it render a reusable shape from props? | src/components/ |
| Would it still make sense after a full rebrand? | src/components/ |
| Would you delete it when replacing the homepage? | src/blocks/ |
The point is not folder purity. The point is making the next rewrite obvious.
What to change when starting a real project
When ShipAny Next is used as a base for a new product, the fastest clean path is:
- Keep
src/components/*andsrc/components/ui/*. - Rewrite the landing blocks in
src/blocks/*. - Rewrite the
landing.*message keys inmessages/en.jsonandmessages/zh.json. - Recompose the homepage in
src/routes/index.tsx. - Keep the SaaS engine, auth, payments, credits, admin pages, and reusable UI primitives intact.
That is much cheaper than editing a giant homepage component where business copy, layout primitives, pricing behavior, and navigation are all tangled together.
A concrete example: pricing
Pricing is the easiest place to see the split.
The pricing block may decide:
- which plans exist
- which features appear under each plan
- which plan is featured
- which payment providers are enabled
- what the button text says
- what happens after checkout succeeds
The pricing table component should only render a pricing table from structured props and call onCheckout when the user chooses a plan.
That keeps the billing story flexible while preserving the table UI. A new product can rewrite plan copy and product IDs without rebuilding tabs, badges, feature rows, responsive columns, or checkout button states.
A concrete example: blog cards
The blog section block can fetch or receive posts, choose how many to show on the homepage, format the locale-aware date, and decide the section heading.
The BlogCard component should only render:
- title
- description
- image
- date
- author
- link
If the homepage later changes from "latest articles" to "customer guides," the card does not need to know. The block changes the story; the component keeps rendering cards.
Common mistakes
The split breaks down when reusable components start reaching upward into app-specific concerns.
Avoid these patterns:
- importing
mfrom@/paraglide/messages.jsinside a generic component - hardcoding homepage links inside
SiteHeader - making
PricingTableknow product IDs directly - putting database or server-only module imports inside visual components
- building one huge landing page component because it feels faster today
The first version may feel convenient. The second project pays the bill.
What this means for rebrands
During a rebrand, the disposable layer should absorb most of the work:
- new headline
- new navigation
- new product categories
- new feature order
- new screenshots
- new pricing names
- new CTA language
- new FAQ framing
The durable layer should barely move:
- buttons
- cards
- dialogs
- tables
- sidebars
- pricing table layout
- form fields
- authenticated app shell
That is the test. If a rebrand forces you to rewrite primitives, the primitives probably knew too much.
How page files stay small
Routes should mostly compose blocks.
For a marketing homepage, the route should read like a table of contents:
function HomePage() {
const { posts } = Route.useLoaderData();
return (
<>
<Header />
<ShowNotesLandingPage variantKey="podcast" />
<Blog posts={posts} />
<Footer />
</>
);
}
The route decides composition. Blocks decide product content. Components render reusable UI.
That is the whole model.
The useful mental model
Think of blocks as adapters between your product story and your design system.
They translate:
- message keys into labels
- product strategy into page sections
- pricing configuration into card props
- route data into visible content
- locale-aware copy into reusable UI
Components should not care where that content came from. They should only care whether the props are valid and how to render them accessibly.
Final rule
If you are customizing ShipAny Next, delete and rewrite blocks without guilt. They are supposed to be product-specific.
Be more careful with components. They are the parts you want to keep across launches.
That is the architectural split: blocks express this product; components survive the next one.
Related articles
- How to Turn a Podcast Transcript Into Show Notes
Learn a simple workflow to turn a raw podcast transcript into publish-ready show notes with timestamps, links, and a clean final edit.
- Podcast Show Notes Examples You Can Copy
See 5 podcast show notes examples you can copy, from short app descriptions to long-form episode pages with timestamps and links.
- What Are Podcast Show Notes?
Learn what podcast show notes are, where they appear, what they include, and how they differ from descriptions and transcripts.