Separating the site, core functionality and rendering the markup

To summarize in one sentence, what I’m trying to achieve: I’d like to separate the common core functionality of all of my sites from the site packages and have dedicated/interchangeable packages which render the html.

My setup

I have a multisite setup with four sites, currently. Every site does have a distinct site package:

- My.SiteA
- My.SiteB
- My.SiteC
- My.SiteD

There is a base package which handles most of the logic: My.Core. My.Core also defines the Document, Component and Content nodetypes which My.SiteA-D use. Basically My.SiteA-D are “empty” site packages, because they do not define nodetypes or have fusion prototypes, unless they need something specific for this site. So far, no problems, if every site would look alike. Unfortunately they don’t.

So, I decided to create dedicated “theme” packages in order to render the different looks of the websites. I did this, because I want to be able to switch the themes easily without touching the site package. So, I created the packages:

- My.ThemeOne
- My.ThemeTwo
- My.ThemeThree (for site My.SiteC and My.SiteD)

The problem(s)

Every node is of the base nodetype defined in My.Core. E.g. My.Core:Document.SomePage or My.Core:Component.SomethingFancy. While intended at first this has the drawback that I can’t create a document node for just one of the sites.

If I create a document node for a specific site, e.g. My.SiteB:Document.Instrument, which won’t ever be used in one of the other sites, I can’t exclude it via constraints in the backend of the other sites. I could only set a constraint for the base My.Core, but that would affect My.SiteB as well and is therefore useless.

To solve this, I could create nodetypes for every site, which just inherit from My.Core:

'My.SiteB:Document.SomePage':
  superTypes:
    'My.Core:Document.SomePage': true
    'My.SiteB:Constraint.AllowInSiteB': true
#...
  constraints:
    '*': false
    'My.SiteB:Constraint.AllowInSiteB': true

But (and here my problem kicks in): Creating site specific nodetypes breaks the “template mechanism”. Because right now the nodetype rendering relies on every node type being a My.Core:* nodetype. This is achieved by an additional matcher case for the root fusion path:

root {
	themedDocumentType {
		@position = 'before documentType'
		condition = Neos.Fusion:CanRender {
			type = ${q(documentNode).property('_nodeType.name') + '.' + Configuration.setting('My.Core.templatePackages.' + site.name)}
		}
		type = ${q(documentNode).property('_nodeType.name') + '.' + Configuration.setting('My.Core.templatePackages.' + site.name)}
	}
}
#Settings.yaml
#----
My:
  Core:
    templatePackages:
      site-a: 'My.ThemeOne'
      site-b: 'My.ThemeTwo'
      site-c: 'My.ThemeThree'
      site-d: 'My.ThemeThree'

E.g.: On the site My.SiteB the document nodetype My.Core:Document.SomePage would be rendered using the prototype My.Core:Document.SomePage.My.ThemeTwo (which is defined in the package My.ThemeTwo). This works only with a common nodetype.

Also: If I would create site specific nodetypes, the theme packages can’t just define one prototype to render a core nodetype. In order to render the nodetypes for Site.C and Site.D, My.ThemeThree would have to define the prototypes My.SiteC:Document.SomePage.My.ThemeThree and My.SiteD:Document.SomePage.My.ThemeThree.
Furthermore I could not switch the themes easily by changing just one line of code in Settings.yaml, because I would have to make sure, that e.g. My.ThemeOne would define the correct nodetypes.


To sum it up, I’m stuck here. Maybe there’s a simple solution, I don’t see. I’m overcomplicating things probably, too. To summarize in one sentence, what I’m trying to achieve: I’d like to separate the common core functionality of all of my sites from the site packages and have dedicated/interchangeable packages which render the html. So, have basically a integrational and a presentational and a site package.

Is that possible somehow? If not, would appreciate any hints how I could use a better approach.



One idea I came up with is, choosing the prototype which renders a nodetype by exchanging the package name:

prototype(My.Core:NodeTypeRenderer) < prototype(Neos.Fusion:Component) {
    fullNodeTypeIdentifier = ${q(node).property('_nodeType.name')}
    nodeTypeName = ${String.split(this.fullNodeTypeIdentifier, ':')[1]}
    themePackage = ${Configuration.setting('My.Core.templatePackages.' + site.name)}

    renderer = Neos.Fusion:Renderer {
        type = ${props.themePackage + ':' + props.nodeTypeName}
    }
}

So, My.SiteB:Document.SomePage would be rendered using My.ThemeTwo:Document.SomePage and My.Core:Component.SomethingFancy would be rendered using My.ThemeTwo:Component.SomethingFancy. The problem is, that this could affect third party packages.

Hi,

have you thought about defining your site specific node types as abstract and then “activating” them in a yaml file in a site specific context folder?

Hi @sebobo,

actually, no. How would

work? Is there a link for a documentation on that? I’m not quite sure how to do that, but it sounds interesting, definitively.

In general you have some advantages when using a different FLOW_CONTEXT for each site when configuring them.

Creating subfolders in your sites Configuration folder with their respective names and settings this sub context via the webserver allows you to have a site specific configuration and also load NodeTypes files only for that site.

So in you vhost you would set the context to FLOW_CONTEXT=Production/mySiteA and then have a subfolder in your Configuration called Production/mySiteA.
Note: this will create separate cache entries for compiled classes etc…

1 Like

The context stuff is explained here: https://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartII/Configuration.html?highlight=context#contexts