Skip to content

Development notes

How responsibilities change when a Vue SPA becomes Astro routes

Trace project and note pages to see what routes, layouts, content rules, and browser scripts each decide, without replacing a removed SPA with another component that owns everything.

林予晴
Published
  • Astro
  • Architecture refactoring
  • Separation of responsibilities
  • Progressive enhancement

Moving from a Vue SPA to Astro does not remove the work the application used to do. URLs still need to resolve to content. Pages still need titles and navigation. Projects need an order, and controls need to respond to input. Removing the Router and global state removes one implementation; it does not yet answer who takes over those responsibilities.

On this site, we separated them. Native pages handle URLs, layouts assemble the shared shell, content selection follows its own data rules, and browser scripts handle local interactions. I care less about whether the directory looks tidy than whether changing a rule makes it clear which outputs it affects.

The inventory before the rebuild first asks which requirements need to survive. This article takes the next step: where those responsibilities go once we decide to keep them.

Separate the initial page from behavior after arrival

Project titles, descriptions, images, and external links should be in the HTML readers receive. Category switching, masonry layouts, and animation may need scripts in the browser. Appearing on the same screen does not mean they need to share a single runtime state.

The table below summarizes responsibilities in the implementation recorded by the original article on October 12, 2026. It is not an architecture Astro requires every project to use.

Layer What it decides What it should not also take on
Route page The language, layout, and content for a URL; which pages a dynamic route generates Every card’s presentation details and the state of every control
Shared layout The HTML shell, metadata, navigation, footer, or shared article structure Data selection and business rules for every page
Content and data rules Project order, article identity, publication eligibility, and query results DOM events, scroll position, and focus
Content component Readable content assembled from the supplied language, data, and copy Rewriting publication eligibility or site-wide navigation rules
Browser script Local interactions, layout updates, and the lifecycle of related resources Waiting until after arrival to add the main content, or rebuilding site-wide routing

Astro’s official documentation describes a layout as an Astro component that provides reusable UI structure, not a special kind of application container.[1] That distinction lets me avoid recreating an App that manages every page inside the new layout.

Follow responsibilities through the project page

The site’s Traditional Chinese project URL is /about/work. This is a Traditional Chinese route example. The route page selects Traditional Chinese and places the project content component inside the shared shell. The project component then reads the sorted project data, applies the existing exclusions, and renders cards.

The order in the data layer and the set of projects shown on the page are separate concerns. The sorted data also contains items not shown publicly. Sharing a data source does not mean the route page should display all of them. Knowing that it is a project page does not give the route responsibility for redefining public content.

Cards have their own responsibilities too: selecting a title for the language, displaying the date and image, and showing a source-code link only when the data actually supplies one. These presentation conditions do not need to become a site-wide store.

By this point, readers can already see the projects in the HTML. Scripts then handle switching between the development and design sections and laying out the cards. They do not need to fetch the project data again before the first card can appear.

Notes add content rules, but routes should not invent them

The project page has a fixed entry point; note articles need URLs generated from their content. The site’s public article routes obtain the published collection from getPublishedNotes(), select articles by language, build paths with slug, and use stableId to find translations of the same article.

There is a deliberate boundary here. The route decides which query results become pages, while shared content rules decide what qualifies for publication. Each route does not rewrite its own draft and date conditions.

Once an article is selected, the article layout renders its body, combines the author and dates, generates a table of contents from the body’s headings, and passes article metadata and links to available translations to the site layout. The site layout then outputs lang, canonical, hreflang, and shared navigation.

Whether an article is public, how it is read, and how a page identifies its URL are connected but distinct questions. They need consistent data, not a single component that contains everything.

For the full publication-eligibility contract, see the shared publication query. Here, I am placing that contract back into the overall flow of responsibilities.

Browser scripts handle interactions, not page identity

In the implementation recorded on October 12, 2026, project switching used #development and #design. Native links first preserve recognizable destinations. Scripts then read the hash and update the visible section and selected state. Language and article identity remain determined by URLs and build-time data; switching a category does not change them.

This arrangement also gives the no-JavaScript state a concrete meaning. Both project sections are already in the HTML, so readers can still read them and use native links. They simply do not get the switching and layout effects supplied by scripts. Readable without scripts does not mean every interaction behaves exactly the same without them.

Astro’s official documentation explains that components can handle interactions with <script> and addEventListener, without bringing in a UI framework just to bind events.[2] Native events do not, however, automatically handle cleanup or restoration. The site’s project scripts still need to manage pending animations, ResizeObserver, and listeners. Leaving the page and restoring it from BFCache also need their own assigned responsibilities.

This article describes the separation found in the code. It does not present cleanup branches identified in a code reading as evidence that every browser lifecycle has been tested.

A shared shell does not mean shared ownership of every decision

Writing every page difference as a long sequence of conditions inside a layout can also reduce the number of files. But adding an article, adjusting which projects are shown, or even changing a title may then require editing the same oversized component.

I decide where a rule belongs by asking what would cause it to change. Article publication eligibility changes with the content contract. Card appearance changes with interface design. Site metadata output follows a shared format. If the reasons for changing them differ, there is no need to force them together merely because they can be called shared.

Sharing does not prohibit queries across layers either. The site’s article layout queries public articles in the same language for further reading. What matters is that it reuses the published collection instead of defining another eligibility policy while displaying related content.

Follow outputs when making changes, not just the layer a file belongs to

The following checks follow from this division of responsibilities. They are not new test results from the work described in this article.

  • When changing project data or exclusions, inspect card content, count, and order, rather than only checking that the data file imports.
  • When changing article publication eligibility, check the home page, lists, article routes, further reading, and sitemap together, so no output keeps using an old condition.
  • When changing layout metadata, inspect the actual HTML for the affected languages, rather than merely checking that the page title looks correct after browser scripts run.
  • When changing interaction scripts, check the initial HTML and readability without scripts as well as the state after interaction. Also determine how the animations and listeners they create stop or resume.

Once responsibilities are separated, checks cannot stop at the edited file. A rule has its own source, but its effects travel through the data to the page. Inspecting both ends tells us whether a change reached its actual point of use.

Scope of this record

This article draws on the site’s project routes, site and article layouts, project components and scripts, and public-note query as recorded on October 12, 2026. It describes the separation of responsibilities in a native Astro static site. It does not cover authentication, remote data operations, or database transactions, and it does not argue that applications with substantial shared real-time state should all remove their UI frameworks.

This division is not proof of performance, SEO, compatibility with every device, or production deployment. For the framework choice, see the Vue-to-Astro rebuild record. Whether all visible behavior was preserved still needs to be answered through the relevant output and interaction checks.

Sources

[1] https://docs.astro.build/en/basics/layouts [2] https://docs.astro.build/en/guides/client-side-scripts