Passing a bunch of components via <Provider> to child components (I know, weird right?)
Unanswered
Transvaal lion posted this in #help-forum
Transvaal lionOP
I have a multi-tenant application which needs to be able to render a front-end for many different brands. We solved this by making a "theme bundle" (a map of components) for each brand, and that gets passed to child components via a
Now I'm trying to migrate to either Pages or App router, but am hitting issues with building and serialization. The way I'm using React Context doesn't seem compatible with how Next serializes things over the server/client boundary.
Have any of you successfully passed components via React Context using Next? If so, I'd love to know how you made it work. Thanks! â¤ï¸
<Provider> at the top. This lets any component in the app render that brand's version of <Ribbon />, for example, without needing to know about the concept of different brands. It works great with React Router.Now I'm trying to migrate to either Pages or App router, but am hitting issues with building and serialization. The way I'm using React Context doesn't seem compatible with how Next serializes things over the server/client boundary.
Have any of you successfully passed components via React Context using Next? If so, I'd love to know how you made it work. Thanks! â¤ï¸
64 Replies
Since the advent of RSCs, you can't pass components down from a server component to a client component as props cause they can't be properly serialised.
I'm assuming you have a layout that wraps your app and is a server component. If that's the case you're going to run into that issue unless you make your whole layout a client component.
An easier solution would be to make a generic component that dynamically imports the right one based on a prop like
I'm assuming you have a layout that wraps your app and is a server component. If that's the case you're going to run into that issue unless you make your whole layout a client component.
An easier solution would be to make a generic component that dynamically imports the right one based on a prop like
brand. So you would use context not to pass around components but just to share the brand information that is then going to be used to import the right component. Does it make sense?Transvaal lionOP
that makes sense! Would I run into issues if my dynamic import is literally just a variable? I'd expect that'd short-circuit a lot of bundling optimization that Next might otherwise do.
I don't think you would have issues with that. I mentioned dynamic imports cause they help reduce the final bundle size and it could be useful especially if you're loading a lot of components.
Transvaal lionOP
OK cool, I might give this a try then. Thank you!
Let me know how it goes ✌ðŸ»
Smells a lot like what we did in Vulcan
dynamic import has technical consequences though, it's not something I would use for personalization if it can be avoided
did you consider using a route parameter to identify the tenant? You would get the current org from this parameter and render accordingly, using statically known components
for the record we studied the ability to swap components in Next a while ago
which seems close to your multitenant issue, our problem was eg using Bootstrap insead of Material UI to replace the core components of a framework
but honestly this kind of stuff tend to be super overkill
The right solution tends to depend on the level of personalization you need per org but you'll want to stick as much as possible to identifical component + pass the org name as an identifier, maybe a config specific to the org that you download before hand, and in extreme scenarios dynamically load a specific component if the component is very different for each tenant
If in doubt listen to Eric, he's got way more experience than I do 👀
@not-milo.tsx If in doubt listen to Eric, he's got way more experience than I do 👀
dangerous advice lol I mostly come here to learn new stuff
but thanks 🙂
Transvaal lionOP
@Eric Burel We can't use a route parameter unfortunately... we pass the tenant as a header, but we can't put it in the route since these different tenant sites all live on their own different domains.
A bit more color about what we're specifically doing:
We have components throughout the site: some are simple components (identical across tenants), and some are customizable per-tenant (with a default). This means the parent of a customized component is often a simple component.
We have components throughout the site: some are simple components (identical across tenants), and some are customizable per-tenant (with a default). This means the parent of a customized component is often a simple component.
This allows us to bootstrap a site and it'll use all the defaults, and then we customize components one-by-one as needed. This is part of how we can get huge code reuse, but with arbitrary customizability per tenant.
@Transvaal lion <@769111741098622976> We can't use a route parameter unfortunately... we pass the tenant as a header, but we can't put it in the route since these different tenant sites all live on their own different domains.
it's usual to do an URL rewrite to put the tenant into a route param
having different domains actually makes it easier
don't know if you know these resources
Transvaal lionOP
ah yes, I've looked at them, but it was a few months ago
they have
size/[id] as the tenant, presumably, but we need it to not show up in the URL.a rewrite won't show up
only redirect do
@Eric Burel https://github.com/VulcanJS/Vulcan/issues/2549
Transvaal lionOP
"Magical replacement of components" is almost what we need. We need something that can do it at runtime.
hmm runtime stuff are the last resort in Next, with an URL rewrite to a route param, you'd be able to do some stuff dynamically or even statically
this can bring perceived perf improvements
Transvaal lionOP
Maybe I'm not understanding somethign about what you're saying with rewrites
(last resort is a bit excessive it's ok to use runtime replacement everywhere if that makes sense in your app)
URL rewrites points the URL visible to the user to a specific page in your app
the user doesn't know it
so I can point "foo.com" to "app/foo/page.tsx"
where foo is a value for [domain]
then all pages under the param will know their tenant, during server render
so you can have static multi-tenancy
Transvaal lionOP
right -- so, we render a different page, which is cool. Here's the issue:
Many tenants will have the literal same page, and it'll be a component 1 or 3 or 5 levels deep that's customized
Many tenants will have the literal same page, and it'll be a component 1 or 3 or 5 levels deep that's customized
oh
so, humm....
yeah then component 5 level deep gets the param from the page if an RSC (https://github.com/manvalls/server-only-context) or via Context if client
Transvaal lionOP
so I'd make a page.tsx for every tenant, and those could render whatever shared components, which render other components that are customized per tenant, and
ahh interesting.....huh
yeah I see what you mean
you can also have explicit pages for tenant
they will have priority of the param
so
- [domain]/page
- foo/some-specific-page/page
- foo/some-specific-page/page
it's ok to have both for some pages for instance
so you can have a variety of patterns depending on the customization level you need for a page
Transvaal lionOP
wow, this is pretty hard to wrap my brain around... I have to run for a bit, but will come back to this. It's heartening to hear that this might actually be possible
This is specifically possible with app router, or pages router, or either?
both it doesnt' change much
in app router you have RSCs so you might be confused by props drilling the tenant id but that's ok
Vercel Platforms is up to date to the app router
Transvaal lionOP
just to make sure I understand...
* I make a root page per tenant with a rewrite
* multiple components down in the tree know what tenant they are
* when I dynamically import from child components, Next will be smart enough to build just what it needs (is this true?)
(the big issue I've had with app router is the dev perf is untenable, and ram usage is ~12-14 GB... I think that's because it's building all the tenants because it doesn't know how to follow only the relevant brand)
* I make a root page per tenant with a rewrite
* multiple components down in the tree know what tenant they are
* when I dynamically import from child components, Next will be smart enough to build just what it needs (is this true?)
(the big issue I've had with app router is the dev perf is untenable, and ram usage is ~12-14 GB... I think that's because it's building all the tenants because it doesn't know how to follow only the relevant brand)
@Transvaal lion just to make sure I understand...
* I make a root page per tenant with a rewrite
* multiple components down in the tree know what tenant they are
* when I dynamically import from child components, Next will be smart enough to build just what it needs (is this true?)
(the big issue I've had with app router is the dev perf is untenable, and ram usage is ~12-14 GB... I think that's because it's building all the tenants because it doesn't know how to follow only the relevant brand)
you can use getStaticParams to scope building to only certain tenant especially in dev
normally it build pages only when accessed in dev
@Transvaal lion just to make sure I understand...
* I make a root page per tenant with a rewrite
* multiple components down in the tree know what tenant they are
* when I dynamically import from child components, Next will be smart enough to build just what it needs (is this true?)
(the big issue I've had with app router is the dev perf is untenable, and ram usage is ~12-14 GB... I think that's because it's building all the tenants because it doesn't know how to follow only the relevant brand)
yeah normally, I would double check for dynamically imported component
again that's really a last resort it's more efficient to have a generic component + some config, if the config grows way too big then yes it's time for a custom component dynamically loaded maybe
Transvaal lionOP
I'm guessing you're talking about
generateStaticParams [here](https://nextjs.org/docs/app/api-reference/functions/generate-static-params#generate-params-from-the-top-down).