rfcs
rfcs copied to clipboard
pluggable child apps (multiapp)
- POC: nuxt/nuxt.js#5818 (closed due to complexity on code-base and limitations)
- Nuxt2 solution for extending: https://github.com/nuxt-community/nuxt-extend
Background
When the project structure grows, sometimes it is not convenient to use a flat directory structure or keep everything in a single repo.
Why?
- A simple way to split big projects into separate parts with separate business logic
- We already have
srcDir
implemented in Nuxt. This feature needs a small refactor effort and does not makes core complicated.
Proposed Solution
One can structure a project like this:
├── shop
| ├── components
| | └── nav.vue
| ├── layouts
| | └── admin.vue
| ├── middleware
| | └── admin.js
| ├── pages
| | └── shop
| | └── index.vue
| └── store
| └── admin.js
├── nuxt.config.js
├── package.json
├── pages
| └── index.vue
└── store
└── public.js
The shop/
dir should basically follow the same standard nuxt directory structure. Builder module combines all children app to the main one. And also creates aliases so components inside admin can be required using:
import Nav from '~shop/components/nav
For registering admin
app, it should be registered inside nuxt.config
. (Items can be npm packages or local dirs)
export default {
apps: [
'~/shop'
]
}
Limitations
These are problems with current POC. We can progressively enhance this functionality and workaround these limitations.
- Currently, store and middleware cannot be supported because
srcDir
is hardcoded invue-app
templates - There is no namespace support. (adding
admin/
prefix to store modules and pages) -- children will be registered with the same namespace. - Just like the main project, child app files are not lodash templates. We can add a
template
flag to support and copy the template child apps to the.nuxt
Do we really need care of this? IMHO it easier create 2 separate apps with different Nuxt config, packages.json and build/deploy them independently. Otherwise we got big monolith which is hard to maintain.
@Koc Exactly that's also my concern/preference to use separate projects. But I know monolith projects that may benefit from this feature to structure their project better. Also, we have the possibility to add parts using an NPM package. (Probably admin
was not a good example tough!)
Also, we have the possibility to add parts using an NPM package.
For sharing some code between apps we can use monorepo approach, extract common business logic/api clients/etc into separate package and require it using path package or npm-link or tools like Rush
Agree with @Koc concern, we should consider more about this.
- pages/routes priority in different apps because different apps may contain same route
- global/scoped middleware/plugin which in run globally or only in specific app
- complexity for development
- avoid influence on traditional usage (performance and configuration)
In my perspective, I would like leverage yarn workspace
for mono repo, like:
├── cms
| ├── pages
| └── store
| ├── nuxt.config.js
| └── package.json
├── app
| ├── middleware
| ├── pages
| ├── nuxt.config.js
| └── package.json
└── package.json
For common app, we already provide components
as a dir to put reusable stuff in:
├── pages
├── store
├── components
| ├── cms
| └── app
├── nuxt.config.js
└── package.json
If necessary, it would be better to use module or sth like extend
to import reusable components or pages.
@clarkdo I see your point about route conflicts. that's also mentioned in the notes to be supported
About mono-repo yes that's one solution for separating admin/cms I personally use (and also common/
dir for shared components/base config)
The Purpose of this enhancement is not having different parts (admin/cms/mobile) and not a good practice to use it for that. but splitting the main project into separate and reusable modules. The increasing number of pages for an enterprise project is unavoidable and there is no way to split it into smaller modules.
One other point is that client-side routing works across this sub-apps.
Yes, please! It would be great to have this feature.
I'd prefer this to losing the state on micro Frontends. Maybe pairing it with some best practices guidelines would make this more resilient
We've been experimenting this in a PoC at my company. We were able to inject sub-apps routes via Nuxt modules. And were proposing this for one of our products.
I love to see we are aligned, @pi0. Given it's a very specific case that most of Nuxt apps are not going to use, I hope it won't affect the size of their bundles.
@clarkdo is pointing fair concerns.
I think Nuxt core is mature enough to envision this but our current file structure isn't. Therefore a lot of things that come after (in how Nuxt wires up its parts) are coupled with this file structure.
Think about backend architectures. For example, the Hexagonal Architecture. If Nuxt core was really a core, it wouldn't care if there are 1 or 2 apps. Or 1 or 2 UI systems.
Replying to @clarkdo's points:
- The different apps shouldn't share routes, because each one could be enforced to mount in a different basePath (
/
,/app
) or baseURL or subdomain. - global/socped plugins and midldewares are indeed needed
- development complexity could be improved with a better CLI. Same thing Guillaume Chau is working on for Vue.
- Traditional biases is a good one. I wouldn't like Nuxt to become Angular.
This keeps coming to my head. If there is no consensus, how about creating a module that ~tries to handle~ handles this without affecting the rest of the user's performance? cc/ @pi0 (❤️)
@pm-LTK This does not affect the user's performance at all. All sub-apps are finally merged into the same data structure. We just glob multiple directories.
I also thought about the module, but that module should duplicate most of the logic of @nuxt/builder
package which introduces inconsistencies. Please refer to related PR to have a better vision of this change.
It would be a VERY GREAT feature, in my case for multi-domains support 🙂 Especially to have different pages (components, assets and layouts would be shared between domains).
For instance (it is absolutely not for i18n but it's a good understandable example):
├── pages-com <==== https://mydomain.com
| ├── index.vue
| ├── about.vue
| └── contact.vue
├── pages-fr <==== https://mydomain.fr
| ├── index.vue
| ├── about.vue
| └── contact.vue
├── components
├── layouts
├── middleware
├── store
└── package.json
It would fit perfectly for:
- an admin interface, docs, blog (with subdomains)
- maintain an app without having multiple repos
- use shared components/layouts/... but different pages per domain
- ...
I just stumbled across this after making something similar. I created a themeable nuxt application. By extending the vue router.
https://codesandbox.io/s/my-app-9ythm
You set the theme path in the nuxt.config, which essentially could be an environment varible. The code then looks into your themes folder for said theme, and if a page is deinfed for it, use that instead.
That enables you to reuse any existing component code via extends. You can switch out styles/ templates / js as required.
We actually ran into something similar as our project got bigger. But we ended up using a monorepo, with several "seperate" nuxt projects, but all the nuxt projects use a shared component library (see a demo how we did that with nuxt: https://github.com/simllll/nuxt-library-demo) and a "module" library where we abstracted all the shared middleware and plugins. Therefore it's only business logic and pages / custom components that are in the nuxt projects. Furthermore one of the sub packages of our mono rep is a cordova mobile app which is using one of the nuxt projects and builds a SPA app out of it.
I would prefer keeping the code "stuctured" but "seperated". Which I'm unsure if this is the case if nuxt allows sub-apps. It's just one special use case that is covered with it (think about e.g. a storybook for your components, another cordova app, or something else people can come up with.. ;))
So for us, we have all freedom and all advantages combined with a monorep approach (which is also used in different projects and is commonly used everyhwere around ;)) and still have a very strickt seperation of code and logik, therefore I would vote for a better documentaton or CLI for a monorepo setup (with a component library or other templates in it) instead.
See https://github.com/nuxt/nuxt.js/issues/5816 please
Do you think below articles covers some parts of it? If no, what are the limitations? https://www.muhaddis.info/how-to-use-multiple-nuxt-js-applications-on-the-frontend/
I explained at length that this does not cut it for me here: https://github.com/nuxt/nuxt.js/issues/5816#issuecomment-547880873
I explained at length that this does not cut it for me here: nuxt/nuxt.js#5816 (comment)
I go through that I understand that you are looking for something else. Here I'm taking a general opinion the post
I hope the development team nuxt will be able to offer a solution to this problem. It really is a matter of necessity..
Any news on this?
what about https://podium-lib.io approach? I just create a vue csr podlet (podium) and it works fine, and now I want to create a nuxt podlet, but I'm issues to use podium as a nuxt midleware :(
This is something that will bring NuxtJS to the next level. I'm working with the same intention trying to see the best solution, having this native would be amazing.
I have a semi-related use-case that might fall within this RFC, but happy to open a new issue or RFC for discussion if the concepts are different enough.
I'm trying to figure out if it's possible to hydrate 2 Nuxt micro-apps on the same page (i.e. a header and a footer when the entire page is not necessarily controlled by Vue/Nuxt). I've made some progress but I still have some more investigation to do into whether I can support this with existing hooks/modules or not.
We're currently doing this through a manual vue-server-renderer
based application, but are looking to migrate to Nuxt and this seems to be the last hurdle we need to tackle. To keep this comment short, I won't go too deep into the implementation details, but I can put together a small POC repo with an example if that would be helpful.
Our use-case is a route-by-route migration of a large production site over to Vue SSR. Instead of a big-bang migration from our legacy architecture to Vue, we've been moving route-by-route. We have a small proxy that directs supported routes to the new vue-server-renderer
-driven Vue SSR app, and unsupported routes to the legacy app. In order to maintain a consistent look and feel during the migration - we chose to export the header and footer Vue components as small consumable micro-apps that could be ingested by the legacy app.
The legacy server makes a call such as https://new-app-server/ui/header
and gets back an HTML fragment and corresponding styles/scripts from the Vue app that it can inject into it's rendered HTML. In order to avoid duplicated styles/scripts we have query params that can tell the footer orchestration call to skip including <link>
/<script>
tags.
Then in the manual vue-server-renderer
-driven app, we have a small bit of logic in entry-client.js
that instead of just mounting to div#app
, loops through looking for multiple mount-points based on data attributes and mounts them using the same Vuex store
across the apps (since the header/footer share a Vue store in the main app). Sharing the Vuex store works seamlessly thankfully.
Routing poses an interesting challenge. In our case, we don't have any needs for client-side routing from the header or footer since the legacy app is not a SPA. There's a few potential options here:
- Don't include a router, but can cause issues if the existing header/footer access
this.$route
/etc - Use a single route like
{ path: '*', component: HeaderComponent }
, but that can conflict ith the normal Nuxt router entries and also have some weird side effects if the legacy app is doing any of its ownpushState
logic - At the moment, we've written a little stubbed
OrchestratedRouter
library that is effectively api-compatible withVueRouter
but just no-op's on the majority of hooks. It's probably no the ideal solution but it has worked for our use-cases thus far.
I'm curious if this is something that this RFC might enable via Nuxt or that would be en entirely new RFC?
So far, I've gotten pretty close in Nuxt with 3 main changes:
- A new layout that removes the app shell and only renders
<Nuxt />
- A
pages/ui/_component.vue
page that renders the proper component usingrender: h => h(componentMap(this.$route.params.component)
- A root-level
{% if (microApp} %}
branch inapp.html
that eliminates the wrapper<html>
/<body>
markup and only renders styles/scripts and Vue SSR HTML
So the last part that I'm investigating now is whether I have any access to tap into the hydration internals and hydrate multiple apps while sharing a Vuex store.
I took a quick stab at the above in https://github.com/nuxt/nuxt.js/pull/8554. Didn't seem to be able to get to the hydration internals through a module.
This would be amazing, it's not the first time that a client requested a "monorepo" style project where in the same project he'd like to have the frontend but also an admin dashboard and later on decided that he wanted to deploy it as two separate parts. It would be amazing to be able to separate the bigger project into smaller projects with their own bundle
I have a semi-related use-case that might fall within this RFC, but happy to open a new issue or RFC for discussion if the concepts are different enough.
I'm trying to figure out if it's possible to hydrate 2 Nuxt micro-apps on the same page (i.e. a header and a footer when the entire page is not necessarily controlled by Vue/Nuxt). I've made some progress but I still have some more investigation to do into whether I can support this with existing hooks/modules or not.
We're currently doing this through a manual
vue-server-renderer
based application, but are looking to migrate to Nuxt and this seems to be the last hurdle we need to tackle. To keep this comment short, I won't go too deep into the implementation details, but I can put together a small POC repo with an example if that would be helpful.Our use-case is a route-by-route migration of a large production site over to Vue SSR. Instead of a big-bang migration from our legacy architecture to Vue, we've been moving route-by-route. We have a small proxy that directs supported routes to the new
vue-server-renderer
-driven Vue SSR app, and unsupported routes to the legacy app. In order to maintain a consistent look and feel during the migration - we chose to export the header and footer Vue components as small consumable micro-apps that could be ingested by the legacy app.The legacy server makes a call such as
https://new-app-server/ui/header
and gets back an HTML fragment and corresponding styles/scripts from the Vue app that it can inject into it's rendered HTML. In order to avoid duplicated styles/scripts we have query params that can tell the footer orchestration call to skip including<link>
/<script>
tags.Then in the manual
vue-server-renderer
-driven app, we have a small bit of logic inentry-client.js
that instead of just mounting todiv#app
, loops through looking for multiple mount-points based on data attributes and mounts them using the same Vuexstore
across the apps (since the header/footer share a Vue store in the main app). Sharing the Vuex store works seamlessly thankfully.Routing poses an interesting challenge. In our case, we don't have any needs for client-side routing from the header or footer since the legacy app is not a SPA. There's a few potential options here:
- Don't include a router, but can cause issues if the existing header/footer access
this.$route
/etc- Use a single route like
{ path: '*', component: HeaderComponent }
, but that can conflict ith the normal Nuxt router entries and also have some weird side effects if the legacy app is doing any of its ownpushState
logic- At the moment, we've written a little stubbed
OrchestratedRouter
library that is effectively api-compatible withVueRouter
but just no-op's on the majority of hooks. It's probably no the ideal solution but it has worked for our use-cases thus far.I'm curious if this is something that this RFC might enable via Nuxt or that would be en entirely new RFC?
So far, I've gotten pretty close in Nuxt with 3 main changes:
- A new layout that removes the app shell and only renders
<Nuxt />
- A
pages/ui/_component.vue
page that renders the proper component usingrender: h => h(componentMap(this.$route.params.component)
- A root-level
{% if (microApp} %}
branch inapp.html
that eliminates the wrapper<html>
/<body>
markup and only renders styles/scripts and Vue SSR HTMLSo the last part that I'm investigating now is whether I have any access to tap into the hydration internals and hydrate multiple apps while sharing a Vuex store.
Hi,
Did you ever continue with this ? A
Hello guys! Great work so far. Quick question, is this still on the roadmap for Nuxt3?
@MarioC3 Yes! Part of POC is in nuxt-extend which needs to be ported to nuxt kit + some refactors on nuxt3 core to fulfill requirements.
@pi0 Awesome! nuxt-extend is great! Again, thanks for all the work! You guys rock!
@amithcabraal-borngroup Nothing beyond the quick POC in https://github.com/nuxt/nuxt.js/pull/8554 unfortunately
It's coming 👀
https://github.com/nuxt/nuxt.js/issues/13367