In the past, there were some advantages to using multi-origin architectures, but for Progressive Web Apps, that approach presents many challenges. In particular, the same-origin policy, imposes restrictions for sharing things like service workers and caches, permissions, and for achieving a standalone experience across multiple origins. This article will describe the good and bad uses of multiple origins, and explain the challenges and workarounds for building Progressive Web Apps in multi-origin sites.
Good and bad uses of multiple origins
There are a few legitimate reasons for sites to employ a multi-origin architecture, mostly related to providing an independent set of web applications, or to create experiences that are completely isolated from each other. There are also uses that should be avoided.
Let's look at the useful reasons first:
Localization / Language: Using a country-code top-level domain, to separate sites to be served in different countries (e.g.
https://www.google.com.ar), or using subdomains to divide sites targeted to different locations (e.g.:
https://newyork.craigslist.org) or to offer content for a specific language (e.g.
Device/Platform: Using different subdomains, to deliver different versions of a website to different devices. The
mobile.pattern is a common way to separate mobile from desktop in adaptive sites. For example: a site can maintain its desktop version at
https://www.example.comand mobile version at
Independent webapps: Using different subdomains to provide experiences whose purpose differs considerably from the site on the main origin. For example, in a news site, the crosswords webapp could be intentionally served from
https://crosswords.example.com, and installed and used as an independent PWA, without having to share any resources or functionality with the main website.
If you're not doing any of these things, it's likely that using a multi-origin architecture will be a disadvantage when building Progressive Web Apps.
Despite this, many sites continue being structured this way for no particular reason, or for 'legacy' reasons. One example is using subdomains to arbitrarily separate parts of a site that should be part of a unified experience.
The following patterns, for example, are highly discouraged:
Site sections: Separating different sections of a site on subdomains. In news sites, it's not uncommon to see the home page at:
https://www.example.com, while the sports section lives at
https://sports.example.com, politics at
https://politics.example.com, and so forth. In the case of an e-commerce site, using something like
https://category.example.comfor product categories,
https://product.example.comfor product pages, etc.
User Flow: Another approach that's discouraged is to separate different smaller parts of the site, like pages for the login or purchase flows in subdomains. For example, using
When building a site from scratch it's highly recommended to avoid dividing it into subdomains. For existing sites, migrating to a single origin is the best approach.
For those cases where migrating to a single origin is not possible, what follows is a list of challenges, and (where possible), workarounds that can be considered when building Progressive Web Apps.
Challenges and Workarounds for PWAs across different origins
When building a website on multiple origins, providing a unified PWA experience is challenging, mostly because of the same-origin policy, which imposes a number of constraints. Let's look at them one at a time.
The origin of the service worker script URL has to be the same as the origin of the page calling register(). This means that, for example, a page at
https://www.example.com can't call
register() with a service worker url at
Another consideration is that a service worker can only control pages hosted under the origin and path it belongs to. This means that, if the service worker is hosted at
https://www.example.com it can only control URLs from that origin (according to the path defined in the scope parameter), but won't control any page in other subdomains such as, for example, those in
In this case, the only workaround is to use multiple service workers (one per origin).
Caution: Registering, and having multiple active service workers consumes additional resources (memory, CPU, etc.), so use your best judgement on how many active service workers a user will likely need to navigate across the site.
The Cache object, indexedDB, and localStorage are also constrained to a single origin. This means it's not possible to access the caches that belong to
https://www.example.com, from, for example:
Here are some things you can do to manage caches properly in scenarios like this:
Leverage browser caching: Using traditional browser caching best practices is always recommended. This technique provides the added benefit of reusing cached resources across origins, which can't be done with the service worker's cache. For best practices on how to use HTTP Cache with service workers, you can take a look at this post.
Keep service worker installation lightweight: If you are maintaining multiple service workers, avoid making users pay a big installation cost every time they navigate to a new origin. In other words: only pre-cache resources that are absolutely necessary.
Once the service worker is active and running, the same-origin policy also restricts cross-origin requests made inside service workers. Fortunately this has a recommended workaround, which is to use CORS (as explained here). Using the no-cors mode when fetching resources inside the service worker is not recommended.
Permissions are also scoped to origins. This means that if a user granted a given permission to the origin
https://maps.google.com, it won't carry over to other origins, like
Since there's no way to share permissions across origins, the only solution here is to ask for permission on each of subdomain where a given feature is required (e.g. location). For things like web push, you can maintain a cookie to track if the permission has been accepted by the user in another subdomain, to avoid requesting it again.
To install a PWA, each origin must have its own manifest with a
start_url that's relative to itself. This means that a user receiving the installation prompt on a given origin (i.e:
https://section.example.com) won't be able to install the PWA with a
start_url on a different one (i.e:
In other words, users receiving the installation prompt in a subdomain will only be able to install PWAs for the subpages, not for the main URL of the app.
A simple workaround is to use a
start_url with a redirect to the main origin. For example: subdomain
https://section.example.com, can define a
https://section.example.com/pwa, containing a redirect to:
https://www.example.com, making the user always land at the home page.
There's also the issue that the same user could receive multiple installation prompts when navigating the site, if each subdomain meets the installation criteria, and prompts the user to install the PWA.
Mitigate this problem by applying any of the following techniques:
Showing the prompt on a single domain: If the site has many subdomains that pass the installation criteria, the
beforeinstallpromptevent could be used and
preventDefault()called (as explained here), to prevent the prompt from appearing in unintended parts of the site (like subdomains), while continue to show it in other parts (e.g. the home page).
Showing the prompt once, across different subdomains: It's also possible to let each subdomain show the installation prompt. (This can make sense if some subdomains are more frequently visited). In this case, it's important to avoid showing the prompt to a user multiple times. To that purpose, a cookie could be used, to track if the prompt has already been shown, and check for its existence when the
beforeinstallpromptevent is triggered. If the cookie is present, it means that the user already received the prompt somewhere else, so
preventDefault()can be called, to avoid showing it again.
While navigating in a standalone window, the browser will behave differently when the user moves outside of the scope set by the PWA's manifest. The behavior depends on each browser version and vendor. For example, the latest Chrome versions open a Chrome custom tab, when a user moves out of the scope in fullscreen mode.
In most cases, there's no solution for this, but a workaround can be applied for small parts of the experience that are hosted in subdomains (for example: login workflows):
- The new URL,
https://login.example.com, could open inside a full screen iframe.
- Once the task is completed inside the iframe (for example, the login process), postMessage() can be used, to pass any resulting information from the iframe back to the parent page.
- As a final step, once the message is received by the main page, the listeners can be unregistered, and the iframe finally be removed from the DOM.
Caution: The previous technique can help mitigating the potential UI change in a small part of the site, where the user can perform an action in a subdomain and return to the main origin (like in a login flow), but won't be an efficient technique to implement for entire paths, including many pages hosted in subdomains (like entire site sections).
Same-origin policy imposes many restrictions for sites built on top of multiple origins that want to achieve a coherent PWA experience. For that reason, to provide the best experience to users, we strongly recommend against dividing sites into different origins.
For existing sites that are already built in this way, it can be challenging to make multi-origin PWAs work correctly, but we have explored some potential workarounds. Each can come with tradeoffs, so use your judgement when deciding which approach to take on your website.
When evaluating a long-term strategy or site redesign, consider migrating to a single origin, unless there's an important reason to keep the multi-origin architecture.
With many thanks for their technical reviews and suggestions: PJ Mclachlan, Paul Covell, Dominick Ng, Alberto Medina, Pete LePage, Joe Medley, Cheney Tsai, Martin Schierle, and Andre Bandarra.