Back to the blog

How to keep a multi-page website maintainable as it grows

A practical architecture for growing a website without duplicating components, content, styles or metadata, based on two real Astro projects.

Published
Reading time
11 minutes
Topics
Astro · TypeScript · Frontend architecture · Maintainability

A multi-page website remains maintainable when every change has an obvious home. Variable content stays separate from shared structure, components remove genuine repetition, and metadata comes from a common source. If adding one page means duplicating another and remembering five unrelated files, the problem is not the number of URLs. It is the architecture.

I have applied this approach in two different settings. Patricia Pólvora’s website has 17 public pages covering services, reports, publications and contact routes. This portfolio combines projects, articles, service pages and Spanish and English versions. In both cases, unmanaged growth would also have multiplied inconsistencies.

This is how I structure a multi-page Astro website so that new routes do not become new exceptions.

A website does not become difficult at a particular page count

Five pages can be difficult to maintain while fifty can remain predictable. What matters is how many decisions have to be repeated manually.

These are some early warning signs:

  • The header or footer is copied into every page.
  • Contact details are written directly into several components.
  • Every route builds its own <head> and schema markup.
  • The same block exists in several almost-identical versions.
  • Styles rely on global selectors or a fragile import order.
  • Adding a page requires manual changes to the sitemap, navigation and related links.

The cost appears later. A phone number changes in four places, an English page keeps an old sentence, or a new route launches without a social image. None of these mistakes is difficult on its own. The difficulty comes from relying on somebody to remember all of them.

I do not try to remove every repeated line on day one. I first separate what belongs to the whole website, what belongs to a family of pages and what must remain specific to one URL.

I map the content before designing components

The first decision is not technical. It is deciding which information deserves a page and what each URL needs to achieve.

Patricia’s website did not begin as a list of templates. It needed to present three consultancy services, reports, training, publications, press coverage, a career history and several ways to make contact. The architecture had to support three journeys: understand a service, verify experience and get in touch.

MDN describes this organisation of pages and their relationships as information architecture. An initial map helps distinguish a page with its own purpose from a section that belongs inside another page.

For each route, I define four things:

Decision Question it must answer
Purpose What should someone understand or do here?
Unique content Which information does not belong on another page?
Relationship Where does the visit come from and what follows?
Maintenance Who will update this content, and how often?

This map prevents the project from creating pages simply because a template is available. It also reveals families: services with similar structures, projects with common fields, or articles that share author, date and navigation patterns.

I separate four layers with different responsibilities

A multi-page website becomes more predictable when the repository reflects those content decisions.

Layer Responsibility Example in this portfolio
Layout Global structure and shared elements HTML document, header, footer, preferences and analytics
Page or route template Composition for one family of URLs Article, project, legal page or service
Component Reusable block with one clear job Project card, article contents or author block
Content and configuration Data that changes without redefining the structure Projects, posts, navigation, personal details and topic hubs

Astro describes layouts as components that provide reusable page structure. In this project, BaseLayout.astro does not know which post is being read or which project is being shown. It handles the document, shared SEO, header, main content area and footer.

The article routes then add what belongs to that family: editorial header, date, reading time, table of contents, author, BlogPosting schema and navigation between posts. The MDX file holds the content for one specific article. Each level knows enough to do its job without trying to control the entire website.

The current structure can be reduced to this map:

src/
├── layouts/       # global structure
├── pages/         # routes and page templates
├── components/    # reusable blocks
├── content/       # articles and projects
├── config/        # shared data and relationships
└── sass/          # styles grouped by responsibility

This is not a universal folder structure. It is useful here because its names make each change easier to locate.

I centralise data before abstracting interfaces

When something repeats, it does not always need a component. Sometimes it needs a shared data source.

The author name, base URL, social links and service description should not be rewritten on every page. In this portfolio, they live in shared configuration. Templates can consume those values without creating another copy that may drift later.

I use typed collections for groups of content. Projects share fields such as title, summary, role, stack, images and dates. Articles share localised titles, summaries, tags, dates and Open Graph images. The schema turns those expectations into a build-time check:

const posts = defineCollection({
  loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
  schema: z.object({
    title: z.string(),
    title_en: z.string().optional(),
    summary: z.string(),
    summary_en: z.string().optional(),
    publishedAt: z.coerce.date(),
    updatedAt: z.coerce.date().optional(),
  }),
});

Astro Content Collections provide a shared data shape, validate each entry and produce TypeScript types when content is queried. If a date is missing or a field uses the wrong format, I would rather fail the project check than ship the inconsistency.

Centralising data does not mean putting everything in one file. Global details, an editorial entry and a topic-hub configuration change for different reasons. Combining them would only replace many small copies with one oversized file nobody wants to edit.

A component should remove genuine repetition

I extract a component when several pages share structure and behaviour, not merely because two fragments look similar.

A project card is a useful component: it receives data, keeps the same semantics and always resolves the same kind of link. A service-page header and an article header may look related, but they communicate different information. Forcing them into a component full of optional props would make both harder to understand.

I use three checks before extracting one:

  1. The structure appears in more than one place.
  2. The block has a responsibility that can be named.
  3. A consistent change should reach every instance.

If reuse requires props such as isArticle, isProject, compact, showDate, hideSummary and several combinations of them, I have probably joined families that only shared an appearance.

Useful abstraction reduces future decisions. Premature abstraction hides where those decisions are made.

Styles need boundaries too

A component architecture cannot rescue a stylesheet where any selector may affect any page.

In this portfolio, I separate foundations, layout, components and page-specific styles. A colour token can serve the whole website. The main grid belongs to layout. A card keeps its rules with the component layer, and an article-only adjustment does not need to become a global utility.

I organised Sass on Patricia’s website with the same goal: a new page should not require copying complete blocks or overriding old rules. That structure does not prevent every conflict, but it limits where to look when one appears.

I avoid two extremes:

  • One global file where the order of hundreds of rules becomes part of the logic.
  • A utility and variant system designed before the project has revealed its real patterns.

Maintainable styles are not the ones with the most layers. They are the ones that let somebody change a block without visually checking the entire website out of fear of side effects.

Routes and metadata belong to the architecture

A page is not complete because its body renders. It also needs a stable URL, title, description, canonical, language relationships and, where relevant, structured data.

Copying this setup into every page works until a rule changes. In this portfolio, dynamic article and project routes build these signals from each entry’s data. Astro can generate dynamic routes from a collection during the build, so one template controls the family while every entry supplies its own content.

The point is not to automate everything. It is to centralise the rule while keeping each value specific:

  • The template decides how to build a canonical URL.
  • The entry provides its title and summary.
  • Configuration knows the base URL and author.
  • The page schema brings those sources together without rewriting them.

Adding an article therefore does not require copying an existing <head>. It requires completing the fields that the collection validates.

The build should find omissions, not only syntax errors

A maintainable website needs checks that reflect what can break.

For this portfolio, I run Astro’s project check, generate the complete static site and inspect its output. An additional test walks through the published HTML to validate the sitemap, languages, schema markup, images and internal relationships. It does not replace a visual review, but it catches repeatable failures that should not depend on opening dozens of pages by hand.

The aim is not to test every line. It is to cover shared rules whose failure would affect many URLs:

  • Every public entry appears where it should.
  • Localised metadata matches the route language.
  • Social images exist.
  • Topic hubs do not reference missing articles.
  • Important pages retain coherent canonicals and schema markup.

The broader the effect of a shared rule, the more valuable an automated check becomes.

What worked on a 17-page client website

On the Patricia Pólvora project, maintainability did not come from reducing every page to one template. It came from distinguishing what the pages shared and what needed to remain unique.

I centralised repeated data, prepared reusable components and organised the styles so that services, reports, publications and career pages could grow on a shared foundation. Each page kept its own content and metadata because it served a different purpose.

The verifiable result is a live website with 17 URLs in its sitemap, three main services and separate routes for reports, training, press, publications and contact. I do not attribute business metrics I do not have to this architecture. I can verify that the website was built, published and can grow without rebuilding its main structure.

This portfolio applies the same principle to a different set of needs. Here, one entry produces Spanish and English versions, posts are grouped into topic paths and metadata comes from typed content. The implementation changes, but the question remains: how many places must somebody remember to publish one coherent page?

Architecture depends on who will maintain the website

An orderly codebase can still be the wrong solution if the person updating the website needs a dashboard and will not work in the repository.

Before choosing the architecture, I define:

  • Who will change copy and images.
  • How often new content will be published.
  • Whether drafts, permissions or review stages are needed.
  • Who will update dependencies and check deployments.
  • Which maintenance work is included after delivery.

When several people publish frequently, a CMS can reduce editorial friction while adding integration, permissions and technical maintenance. My articles on Astro with Sanity and Keystatic versus Sanity cover those responsibilities.

A website is not maintainable simply because its code is clean. It also needs a realistic process for changing it.

A quick review before adding another page

Before creating a new route, I ask:

  1. Does this page have a distinct purpose and intent?
  2. Does it belong to an existing family or require a different structure?
  3. Which content is unique, and which data already exists in configuration?
  4. Which component removes real repetition without adding unnecessary variants?
  5. Where will its title, canonical, social image and schema come from?
  6. Which check will detect an incomplete route?
  7. Who will be able to update it six months from now?

Clear answers mean that adding pages does not have to add disorder. The important work happens earlier: designing a structure that makes the home of every decision visible.

The Patricia Pólvora case study documents the scope, 17 pages and published outcome of this architecture in a real client project.