What to watch for in a single page application: an SPA checklist for UIUX and PMs
Key points
- A single page application loads one HTML document and swaps content with JavaScript. The user sees a new screen, the browser sees the same document, and that gap gives the back button, shared links, search engines, GA4 and screen readers each their own problem. One section per problem, each with an example.
- Routing: every screen needs its own URL and its own history entry. Filters that vanish when you press back, or a link to a colleague that opens the home page, are states the URL did not carry.
- SEO: Googlebot runs JavaScript, but every page waits in a rendering queue and not every bot runs JavaScript, so Google still recommends that the server send finished HTML. My team met this in 2021 when we moved to ReactJS with several business units on one front end. Moving from client side to server side rendering in the last year or two put the content into the first response.
- GA4: a user taps through twenty screens and the report shows one page view, because the page never reloaded. Enhanced measurement’s history events fill the gap. If two mechanisms send page_view for the same navigation, for example Google Tag Manager and the automatic setting, every view counts twice.
- Accessibility and versions: on a full reload the browser changes the title, moves focus to the top and reads the new page. After an SPA screen change those have to be built, with some help from the framework. A tab left open keeps running old code, so this site checks version.json for a newer release.
Who this is for
UIUX designers and PMs. You do not need to write code, but you will hear SPA, SSR, routing and canonical in meetings, and the design and requirements you hand over will be missing a few states if the product is an SPA. Each section starts with a screen you have probably seen, explains why it happens, then says what to deliver, what to ask engineering and how to check the result. Each technical term gets a plain sentence the first time it appears.
1. What an SPA is, and how it differs from an MPA
Scene. You open a back office, click Orders, Customers and Reports in the left menu, and each screen appears. The address bar changes, but the tab’s reload icon never spins and the document is never reloaded. That is an SPA. Now open an older shopping site: every category click reloads the whole page, the reload icon spins, the progress bar runs. That is an MPA.
What happened. MDN defines a single page application as a web app that loads only one web document, then updates that document’s content with JavaScript when something different should be shown. The upside is no round trip for a whole page, so switching is fast and interaction can be finer. The same entry lists the cost: SEO, more effort to keep state, navigation you have to build yourself, and performance monitoring that gets harder. An MPA (multi page application) has one server rendered HTML document per URL, with a full reload on every change.
Three more words come up in meetings. CSR, client side rendering: the server returns nearly empty HTML and the browser builds the screen by running JavaScript. SSR, server side rendering: the server sends finished HTML, then the browser attaches JavaScript so the page becomes interactive, and that attaching step is called hydration. SSG, static rendering: the HTML is built once at build time and stored as files. web.dev’s Rendering on the Web lays these out on one line, and a pure SPA sits at the CSR end.
Many React projects are not pure CSR. This site’s setup is Next.js outputting the pages as HTML at build time, the browser attaching JavaScript after loading, and links inside the site using client side navigation without a full reload. Next.js also supports rendering on each request, so this is this site’s choice, not what the framework always does. The mix gives the first load real content and later switches the feel of an SPA, and everything below applies the moment a product changes screens without changing the document.
- Switching: an MPA reloads the whole document every time, an SPA changes the screen without changing the document.
- What the browser does for you: on an MPA the back button, the title, focus and what search engines see come from the browser and the server. On an SPA each one has to be built, and the drawing below lists seven.
- What fits: content that has to be found leans MPA or server rendering, signed in back offices with dense interaction lean SPA, and many projects are a mix.
What to tell engineering. “On first open, does the server send HTML or does the browser build it? After that, does switching screens reload the document?” Those two answers decide what else you have to deliver.
2. Routing and URLs: back, refresh, share
Scene one. A user filters the order list to this month, paid, Taipei store, opens the third order, presses back, and all three filters are gone. Scene two. A sales rep pastes the URL of a back office report to a client who has access, and after signing in the client lands on the home page instead of the report. Scene three. A user on a phone presses the back key to close a filter panel and leaves the whole site, back to the previous website.
What happened. All three are the same thing: the screen changed and the URL and the history did not move with it. Users hold three expectations they never say out loud: back returns to the previous screen, refresh stays put, and a pasted URL opens the same thing. A site that reloads whole pages gets all three from the browser. An SPA has to build them: every screen worth returning to gets its own history entry and URL, and closing a panel consumes its entry, so that back closes the panel instead of leaving the site. Scene two adds one more layer: after sign in, the product has to remember where the user was going.
Google Search Central’s JavaScript SEO guide is direct: use the History API for client side routing and do not load different content from a # fragment, because Googlebot cannot reliably resolve fragment URLs. A # fragment does create a history entry, so back usually still works. The difference is that search engines treat it as the same page. The same guide has one more sentence designers should keep: Google can only discover links that are <a> elements with an href. A card built as a <div> that navigates on click is not a link to search engines, to open in new tab, or to the middle mouse button. You can test it yourself: right click the card, and if the menu has no open link in new tab, it is not a link.
Scroll position belongs here too. GA4’s SPA guide points out that the browser usually keeps the old scroll position when an SPA switches screens, so users miss the top of the new screen and scroll depth tracking goes wrong, and it recommends resetting the scroll to the top after each switch. The opposite case exists as well: returning from a detail to a list, users expect to land where they left. The browser exposes scrollRestoration to control this, but someone has to decide both directions first.
What to tell engineering. “Does this screen have its own URL? Should filters, pagination and open panels go into it? Where does back land? After a switch, reset scroll or keep it?” Write the answers into the requirements and label every screen in the design with its URL. Accept with three actions: paste the URL into a new tab and get the same screen, refresh and stay put, go from list to detail and back and find the same filters and position.
3. Loading, error, 404: three states, each drawn
Scene one. A user presses Generate report, nothing changes for three seconds, they assume the tap missed and press again, and the system makes two reports. Scene two. A campaign ended six months ago, its link still opens, the screen says campaign not found, and the server answers 200. Scene three. While reviewing a back office prototype I had built with AI, I logged a finding: after choosing Custom period the words Choose dates appeared and the date picker never did. That screen looked like waiting and looked like nothing, and the user could not tell which.
What happened. A site that reloads has the browser’s progress bar, so users know they are waiting. An SPA switching screens shows nothing. The wait is invisible unless the designer draws it. Next.js turns this into file conventions: loading.js defines the interface shown while a route segment loads, built on React’s Suspense, and error.js catches an error in a segment so the rest of the page stays alive. For designers it means two more deliverables per main screen: what loading looks like, and what the error looks like and whether it can be retried. Scene one also needs engineering to make Generate report produce one report no matter how many times it is pressed. The word is idempotent, covered in the API article. Scene three needs three states told apart: still waiting, nothing to show, and a broken control, and none of them may look like another.
Scene two is a soft 404. Google’s guide explains that client side routing often cannot return a real 404 status code. When a page looks like not found while the server says 200, Google reads the content and may judge it a soft 404, or may index it as a thin page, and neither is what you want. The guide offers two fixes: redirect with JavaScript to a URL where the server really answers 404 (for example /not-found), or add a noindex robots meta tag to error pages with JavaScript. The designer’s job is to make sure the product has a not found screen at all, and that it looks different from loading and from no data.
One more error belongs to an SPA tab left open: after a deploy, the old code asks for the code chunks of its own version, and the new deploy has removed those files. Section 9 covers it.
What to tell engineering. “For every main screen I will hand over loading, error and not found. Does the server answer 404 for the not found page? If not, which of the two fixes do we use?” To accept, paste a URL that does not exist into a new tab and check both the screen and the status code.
4. SPA SEO: Googlebot runs JavaScript, but a page waits in a queue first
Scene. Marketing launches a new product page. Two weeks later a search for the product name finds nothing. Engineering says Google runs JavaScript. You open the page, press Ctrl+U to view the source, and the whole HTML is an empty <div id="root"> with a few scripts. The title is the company name. The product name is nowhere. That does not prove the cause yet, but it is the first thing to check.
What happened. The common line is that SPAs are bad for SEO. The official documents are more precise. Google Search Central states that Googlebot runs JavaScript with an evergreen version of Chromium: it crawls first, queues every page that answers 200 for rendering, renders it in headless Chromium, then indexes. The page may sit in that queue for seconds or longer. The same guide goes on to say that server side rendering or pre-rendering is still a great idea, because it is faster for users and crawlers, and not all bots run JavaScript. The empty HTML in the scene is pure CSR: Google renders it once its turn comes, while other search engines, social previews and AI crawlers may never run the JavaScript.
From that guide comes an SEO checklist for SPAs, with the tool that checks each line:
- Every screen needs its own
<title>and meta description. Setting them with JavaScript is allowed, but in the HTML is best. Check: Ctrl+U shows the raw HTML. - The canonical (the tag that tells Google which URL is the official one for this page) can be set with JavaScript, but must not differ from the value in the original HTML. Check: Ctrl+U, then the Google-selected canonical in Search Console’s URL Inspection.
- Status codes should mean something. Not found returns 404, sign in required returns 401, a move uses a redirect. Where client side routing cannot, use the two fixes from section 3 to avoid soft 404s. Check: the Network panel in DevTools.
- Links are
<a href>, and routing uses the History API. Check: right click and look for open link in new tab. - Google will not render JavaScript from files blocked by robots.txt. What Google finally saw and indexed is checked in Search Console’s URL Inspection, with its rendered screenshot and crawled HTML.
This site makes title, description and canonical part of each page’s metadata and writes them into the HTML at build time, so Ctrl+U shows the full title and body before any rendering. Next.js also supports dynamic rendering and dynamic metadata, so this is this site’s setup, not the only way the framework works. A pure CSR project needs pre-rendering or server rendering arranged separately, and that is a question for the first week of a project. Adding it after launch is expensive.
What to tell engineering. “Does the HTML in Ctrl+U contain the title and the body? Who fills in each screen’s title and description, and where? After launch, who checks in Search Console what Google indexed?”
5. Several business units on one SPA, each with its own SEO
Scene. The company has three business units with three brands on one front end. One day a salesperson from brand A reports that a Google search for brand A’s About us returns brand B’s page. A closer look shows all three About us pages share one block of copy with only the brand name swapped, so Google treated them as one piece of content and picked one as the representative. The way to confirm it is Search Console’s URL Inspection, which shows which URL Google selected as canonical.
What happened. This section is something my team ran into. From 2021 the team moved the front end to ReactJS, and screen changes became an SPA. The first question I asked from the design side was the one in the previous section: can search engines crawl what the page shows? Pure client side rendering gives a crawler nearly empty HTML, with the content arriving only after JavaScript runs. That stayed with us for years. In the last year or two the team closed it with the answer Google’s guide recommends: client side rendering (CSR) became server side rendering (SSR). The server returns finished HTML and the browser attaches JavaScript afterwards. The rendering queue still happens, but the first response a crawler receives already holds the content, so indexing no longer depends on whether the JavaScript ran.
The second question is harder than the first. One front end serves several business units (BUs), each with its own brand, and each needs its own SEO. The code is one thing. What search engines see has to be several websites. In the React refactor article I described the BU override mechanism: colours, logos and copy can be overridden per BU. SEO fields need the same mechanism, and the list is longer than it looks:
- Title and description: every screen of every BU gets its own
<title>and meta description, in that BU’s name and that market’s wording. A shared template is not duplicate content by itself. The same main content is. So the three About us pages in the scene need their own titles and opening paragraphs. - Canonical: Google’s canonical guide says that when pages are duplicates or very similar, Google picks one as the representative, and
rel="canonical"is how you state your preference. It is a hint, Google does not promise to follow it, but leaving it out hands the choice to Google. Each BU points to its own URL, and acceptance means checking the Google-selected canonical in URL Inspection. - Sitemap and robots.txt: our BUs take two shapes. Some have their own domain, some share the main domain and differ only by a path suffix. Google’s sitemap guide says the URLs listed in a sitemap must be on the same host as the sitemap, with cross submission through Search Console or robots.txt for anything else, and robots.txt is one per host. So a BU on its own domain usually gets its own sitemap and robots.txt, or one central set with cross submission. A path only BU shares the main site’s, with every BU URL listed.
- hreflang: Google’s guide on localized versions uses hreflang for language or region versions of the same content. Languages differ between our BUs, so the first step is to sort the cases: the Chinese and English editions of one brand are versions of each other and get hreflang. Two different brands in different languages are not versions of each other and must not be linked that way. The guide also warns that a version whose content is not translated counts as a duplicate.
- Search Console: one common way to manage this is one property per BU. The Search Console help page describes two property types: a Domain property covers the whole domain with every subdomain, a URL prefix property can be limited to a path. A BU on its own domain gets a Domain property, a path only BU gets a URL prefix property. After launch, use the URL Inspection tool, View crawled page and Test live URL, to confirm the rendered page and HTML carry that BU’s own title and content, and not the template defaults.
One shortcut that used to be common is no longer recommended. Dynamic rendering (detect a crawler and serve pre-rendered HTML, serve JavaScript to everyone else) is labelled a workaround in Google’s documentation. The page states outright that it is not a long term solution and recommends server side rendering, static rendering or hydration instead.
What design can do is deliver SEO fields as part of the BU configuration. When a BU is set up, the title, description, share image and canonical rule sit next to the colours and the logo, engineering wires them through the same override mechanism, and every new BU is one more configuration to fill in. In 2021 we patched this while live. Today I put it on the first configuration sheet.
What to tell engineering. “Do each BU’s title, description and canonical come from the BU configuration? Where do the sitemap and robots.txt of a BU on its own domain live? Which pages are language versions of each other and get hreflang? How is Search Console split?” Accept with URL Inspection: one page per BU, checking the Google-selected canonical and the rendered title.
6. GA4 SPA tracking: how page_view is sent without a reload
Scene one. In the first week after launch, GA4 shows 1.0 views per user, while support reports that users move through twenty screens per visit. Scene two. Another project shows the opposite, views at twice the real number, every screen counted twice.
What happened. GA4 collects page_view automatically, and the enhanced measurement documentation lists it among the events that cannot be switched off in that panel. The Measure pageviews page describes the other route: set send_page_view to false in the Google tag to stop the automatic view on page load, then send the event yourself. Scene one happens because the automatic view fires on two occasions, page load and history change, and the second has to be switched on: the Measure single-page applications guide recommends the advanced option inside enhanced measurement, page changes based on browser history events. In plain words, whenever the code formally adds a history entry for a screen (the section 2 behaviour), GA4 sends one page_view. That is also why the URLs in section 2 have to be right before tracking can be.
Scene two is the bolded warning in the same guide: if your tags are configured through Google Tag Manager, do not also enable automatic history tracking in GA4, because views get double counted. The thing to check is whether two mechanisms send page_view for the same navigation. Having GTM by itself does not double anything. I ask this in every tracking specification meeting, because two numbers that disagree are harder to deal with than no number.
The guide also describes verification: enable debug mode, click through the SPA’s screens in DebugView, and each new virtual screen should add one page_view event, with its parameters compared against the previous event. The guide singles out page_location, and I compare page_title with it. This site loads GA4 through gtag.js directly, without GTM, so page_view is left to enhanced measurement’s history events, and the only events sent by hand are the actions automatic collection cannot see.
The same guide ends with the scroll reset from section 2. Screen change, scroll reset and the page_view parameters are tied together in an SPA.
What to tell engineering. “Who sends page_view, the history events or our own code? Can two mechanisms fire on the same navigation? Before launch we click through once together in DebugView, exactly one event per screen.”
7. Accessibility: title, focus and announcements after a screen change
Scene one. A screen reader user presses Next in an application flow, the screen moves to step two, and what they hear is still the title of step one. Scene two. A keyboard user presses Tab more than thirty times on every screen change to get past the left menu and the top navigation before reaching content. Scene three. A form submits, a one second Saved appears top right, and the screen reader says nothing.
What happened. On a full reload the browser does three things for the user: the tab title becomes the new page’s title, keyboard focus returns to the start of the document, and the screen reader reads the new page from the top. After an SPA screen change those have to be built, and the framework covers part of it: Next.js ships a route announcer that reads the new document.title to assistive technology on client side transitions. Without something like it, what the user hears and where they are stay on the previous screen.
The Understanding document for WCAG 2.4.2 Page Titled names SPAs directly: when distinct views are served from the same URL and content is replaced dynamically, the page title should change dynamically too, to reflect the current content or topic. It is the easiest rule to meet and the most often forgotten. On this site every page’s title is written at build time and changes on client side navigation as well.
For focus, the W3C APG guide to keyboard interfaces and WCAG 2.4.3 Focus Order state the principle: focus order follows meaning and operation. On a change to a new main screen, the usual way is to make the main heading programmatically focusable with tabindex="-1" and place focus on it once navigation completes. Filtering, returning from a detail to a list and closing a panel are separate cases, each deciding whether focus stays where it was or returns to the control that opened it. Headings in this site’s articles carry an id and tabIndex={-1}, so jumping from the table of contents lands focus on that heading without adding a Tab stop. Scene one has the same fix: on step two, focus goes to the Step two heading and the title updates with it.
For announcements there are ARIA live regions. MDN describes them as the way assistive technology is told about content that updates dynamically, without the user having to go and look. The region exists first, and the message is placed into it. Put scene three’s Saved into such a region and the screen reader reads it. The result message of this site’s copy buttons and the release notice both use role="status".
Scene two maps to WCAG 2.4.1 Bypass Blocks, the skip to main content link. An SPA’s navigation never moves, so a keyboard user crosses it again on every screen change, which makes this link more important in an SPA than on a traditional site. This site’s layout puts it first in keyboard order.
What to tell engineering. Hand over one table with four columns: screen, title text, where focus lands after the change, which results are announced. Add one line: the skip to main content link is first in keyboard order. Accept by walking the main flow once with the keyboard and once with a screen reader.
8. Performance: the first load and the navigations after it
Scene. The home page carries a 3D showcase, live charts and a chat widget. The first open takes six seconds, after which every click is fast. The boss says it is fast, because their tab has been open all day. A new customer says it will not open, because this is their first visit.
What happened. SPA performance comes in two halves. The first load downloads the main bundle. Later switches usually fetch only data, though a project that splits its code still downloads the chunk for an area the first time the user enters it. web.dev’s Rendering on the Web describes the first half with three metrics: TTFB, the time until the server’s first byte, FCP, the first time content is painted, and INP, the time from an interaction to the screen responding. Server side rendering usually gives a fast FCP and a slower TTFB, client side rendering the reverse, and the bigger the JavaScript, the busier the main thread, the worse the INP.
What design controls is how much the first screen needs. Pack the home page with charts, animation and third party components and the JavaScript grows with it. Next.js’s Link prefetches the targets of links in the viewport, so later switches are fast, but the cost of the first load is real. web.dev’s Web Vitals page names LCP, INP and CLS as the core metrics and gives thresholds. Chrome’s Lighthouse measures loading metrics locally, such as FCP, LCP and CLS. INP comes from real user data, and the Core Web Vitals report in Search Console is one source. Writing the targets into the requirements is far cheaper than arguing after launch.
One more thing concerns the back button. The browser’s back/forward cache (bfcache) can restore a whole page instantly on back, but web.dev’s guide states it only works for browser managed navigations, so soft navigations inside an SPA cannot use it. It still helps when coming back to the SPA from another site. So the back button inside an SPA has to handle state and scroll position itself, which returns to section 2.
What to tell engineering. “How much JavaScript does the first screen load? Can the three heavy pieces on the home page wait until the user scrolls to them? What are our LCP and INP targets, and where do we read the real user numbers after launch?”
9. Versions: a tab left open keeps running old code
Scene. Support calls you: the button I see is different from the one you described, I have no Export. You check. Their tab has been open since the morning, and they never got the version deployed at noon. The worse version: they tap a screen and it goes white, because the morning’s code asked for its own code chunks and the noon deploy had removed those files.
Get Karenya Lin’s stories in your inbox
Join Medium for free to get updates from this writer.
What happened. On a site that reloads, each request usually fetches the latest code, caches aside. An SPA tab open all day keeps using the bundle it loaded that morning, knows nothing about a new deploy, and breaks the moment it asks for a code chunk that has been removed.
The approach this site chose is written up in the release detection article. It is one answer among several, so here are only the decisions. On load, read /version.json once and keep the current id, read it again when the tab comes back from the background, and on a timer. When the id differs, show one line of explanation, then reload, with a guard against an endless reload when an edge node still serves the old version. The payment rule goes like this: while a payment step is in progress the page does not reload, and only a release that changes the payment part itself reloads with an explanation, because that is the one update a paying user must not miss. This site has no checkout, so that rule is kept for products that have one.
Design decides what that one line says, whether the user can postpone, how work is kept while they do, and which states must never be interrupted. A half filled form, a payment in progress and a file upload are the three states to list before anyone discusses automatic updates.
What to tell engineering. “After a deploy, how long until an open tab finds out? Does it warn first or reload at once? Can the user postpone? These three states (form, payment, upload) must not be interrupted, I will list them.”
10. Security and expiry: the token is engineering’s, the expiry screen is design’s
Scene. A user spends twenty minutes on an application form, presses submit, and is thrown to the sign in page. After signing in again they land on the home page, and the twenty minutes are gone.
What happened. SPAs often keep the signed in state in the browser. The OWASP HTML5 Security Cheat Sheet is explicit: do not store session identifiers in local storage, because JavaScript can always read it, and a cookie with the httpOnly flag reduces that risk. That is engineering’s decision. Designers only need to know it exists.
What design owns is the experience after expiry. An SPA tab stays open all day, the session expires somewhere in the middle, the user finds out when they press save, and whether what they typed survives depends on whether someone designed it to. Three questions: is there a warning before expiry? After signing in again, does the user return to the same screen or to the home page? Is half entered content kept? The same three questions exist on sites that reload, and the longer a tab stays open, the more often they come up.
What to tell engineering. “Warn how many minutes before expiry? Return to the original screen after sign in. How long is half entered content kept in the browser? Passwords and card numbers are never kept.”
11. Checklist: twelve questions at the start of an SPA project
- Does every screen have its own URL and history entry? Do filters, pagination and panels count as state?
- Where does back land? After a screen change, is scroll reset or kept?
- Are links
<a href>? Does right click show open link in new tab? - Does every main screen have loading, error and not found states? Do the three look different?
- Does the HTML in Ctrl+U contain the title and body? Is the first load server rendered, pre-rendered or pure client side? After launch, who checks indexing in Search Console?
- When several BUs or brands share one front end, does each BU own its title, description and canonical? How are sitemap, robots.txt, hreflang and Search Console split?
- Are the SEO fields on the BU configuration sheet, delivered with the colours and the logo?
- Who sends GA4’s page_view? Can two mechanisms fire on the same navigation?
- Does the title change after a screen change? Where does focus land? What is announced? Where is the skip to main content link?
- How much JavaScript does the first screen load? Are the LCP and INP targets written down? Where do the real user numbers come from?
- After a deploy, how does an open tab find out? Can the user postpone? Which states must not be interrupted by a reload?
- Is there a warning before the session expires? Where does the user return after signing in? Is half entered content kept?
I use these twelve at the start of every React and Next.js project. For some products the answer to a question is simply not applicable, and that counts as an answer. The ones nobody can answer yet are the decisions to make before the first screen is drawn.
Single page application FAQ
What is a single page application? What does React SPA mean?
A single page application loads one HTML document and swaps content with JavaScript instead of reloading the document. If the screen changes when you click a menu and the tab’s reload icon never spins, that is one. Sites built with React, Vue or Angular are mostly this shape, and a React SPA is simply one built with React. Frameworks like Next.js output HTML at build time while in-site navigation still runs client side, so everything in this article applies to them too.
SPA vs MPA: how do they compare and which should I choose?
An MPA has one server rendered page per URL with a full reload on each change, so back, title, focus and what search engines see come from the browser and the server. An SPA switches faster with finer interaction, but has to supply all of those itself. Content sites that need to be found lean MPA or server rendering. Signed in back offices with dense interaction lean SPA. Many projects are hybrids: the first load comes from the server or the build, and later navigation runs on the client.
Is SEO really worse for an SPA?
Google runs JavaScript with an evergreen Chromium before indexing, but every page waits in a rendering queue first, and not every bot runs JavaScript, so Google still recommends server side rendering or pre-rendering. Check in three layers: Ctrl+U for whether the server’s HTML holds the title and body, DevTools for status codes, and Search Console’s URL Inspection for what Google rendered and which canonical it chose. Four things to get right: a different title and description per screen, a canonical that matches the HTML, meaningful status codes (no soft 404s), and links built as <a href> with History API routing.
How do I track page_view in an SPA with GA4?
Turn on page changes based on browser history events under enhanced measurement, and every time the code adds a history entry for a screen, GA4 sends one page_view. To send it entirely by hand, set send_page_view to false in the Google tag and send the event yourself. Two mechanisms must not fire on the same navigation: with Google Tag Manager’s history change trigger, the automatic side has to be off, or views double. Verify in DebugView by clicking through the screens, exactly one page_view per screen, comparing page_location between consecutive events. A report showing one view per user usually means the history events are off.
Why does the back button break in an SPA?
Because the screen change added no history entry, so the browser’s history has nothing to return to, or a panel added an entry when it opened and did not consume it when it closed, so back carries the user out of the site. Routing with # fragments usually keeps back working, but Google’s guide says it cannot reliably treat fragments as separate pages. Using a <div> as a link loses open in new tab and crawler discovery. The check is simple: on every screen press back once, refresh once, and paste the URL into a new tab, and see where each lands.
Several BUs or brands share one front end. How do we separate their SEO?
Treat the SEO fields as part of the BU configuration: a title and description per screen per BU, a canonical pointing to the BU’s own URL (a hint Google does not promise to follow, but leaving it out hands the choice to Google), a sitemap and robots.txt of its own for a BU on its own domain or one central set with cross submission, the main site’s for a path only BU with every URL listed, hreflang only between language versions of the same content, and one Search Console property per BU as the common arrangement. Accept with URL Inspection, checking the Google-selected canonical and the rendered title. Dynamic rendering is labelled a workaround by Google now, so use server side or static rendering.
What extra screens does a designer deliver on an SPA project?
Three more per main screen: loading, error (with or without retry) and not found. Three more for the whole product: the session expiry warning, the new release notice, and the skip to main content link. Loading and empty must look different, or users cannot tell waiting from nothing. These states can sit in the same design file as the main flow with stable numbers, which removes the back and forth of what shows here when there is no data.
How should title and focus be designed for a screen change?
Give every screen a title in the same format as a full page site (screen name plus site name) and write it into document.title on change. On a change to a new main screen, move focus to its main heading, made programmatically focusable but outside the Tab order (tabindex="-1"). Filtering, returning to a list and closing a panel decide separately whether focus stays or returns to the control that opened it. Announce results such as submitted, saved or switched through a live region that exists before the message does. Deliver one table: screen, title text, focus target, sentence to announce.
Should a panel or lightbox have its own URL? Should back close it?
It depends on whether it can be shared. Product details and filter results that users would paste to someone go into the URL, and back returns to the list. Confirmation dialogs and notices that nobody shares stay out of the URL, but still get a history entry that is consumed on close, so that back closes them instead of leaving the site. On a phone, the back key carries the same expectation as on desktop, which makes this rule matter more on mobile web.
What happens to a half filled form on reload or expiry?
Decide three things first: how long before expiry to warn, whether sign in returns to the same screen or home, and whether half entered content is kept. If it is kept, say for how long, where (in the browser or in the account), and which fields never are (passwords, card numbers). The reload that comes with an automatic release update follows the same rules, with payment in progress and uploads listed as never interrupted.
Why does the social share preview miss the title and image?
Share previews read the Open Graph tags in the HTML <head>. A pure client side SPA leaves those tags for JavaScript to fill, and the bots that build previews do not necessarily run JavaScript. Google’s documentation says the same about bots in general. The fix is the SEO fix: title, description and share image in the HTML the server returns, one set per screen and per BU. Put the share image size and copy on the BU configuration sheet.
References
- MDN, SPA (Single-page application)
- MDN, History API
- MDN, History: scrollRestoration property
- MDN, Navigation API
- MDN, ARIA live regions
- web.dev, Rendering on the web
- web.dev, Back/forward cache
- web.dev, Web Vitals
- Google Search Central, Understand JavaScript SEO Basics
- Google Search Central, Dynamic Rendering as a workaround
- Google Search Central, How to specify a canonical URL
- Google Search Central, Build and Submit a Sitemap
- Google Search Central, Robots.txt Introduction and Guide
- Google Search Central, Localized Versions of your Pages
- Search Console Help, URL Inspection tool
- Search Console Help, Add a website or platform property
- Google Analytics, Measure single-page applications
- Google Analytics Help, Enhanced measurement events
- Google Analytics, Measure pageviews
- W3C WAI, Understanding SC 2.4.1 Bypass Blocks
- W3C WAI, Understanding SC 2.4.2 Page Titled
- W3C WAI, Understanding SC 2.4.3 Focus Order
- W3C WAI, Developing a Keyboard Interface (ARIA Authoring Practices Guide)
- Next.js, Linking and Navigating
- Next.js, loading.js
- Next.js, error.js
- Next.js, Accessibility (route announcements)
- React, Suspense
- OWASP, HTML5 Security Cheat Sheet
- Chrome for Developers, Introduction to Lighthouse
Originally published at karenyalin.com on October 30, 2026. A Chinese version is also on Medium.










