Component comments after thymeleaf render

When fetching somecontroller.get(request, {contentId: siteConfig.footerId }).body from for example page controller, the somecontroller response body is not fully rendered into html, but contain component comments.

I’m guessing these component comments get handled in postProcess.

The problem is that since I’m including stuff from various controllers, the component comments reference components which are not part of the main controller. And you get a null pointer exception.

Is it possible to tell thymeleaf.render to do a full render?

Hmm there seems to be a bug/feature related to this aswell.

I’m writing replaceComponentComments(body) to achieve a full render.

If I have a page P1 which uses page P2.get().body which in turn uses layout L1.get().body
Then L1.regions suddently have added P2’s regionName which looks like this:

"regions": {
        "1": {
            "components": [
                    "name": <snipped>,
                    "path": "P2RegionName/0/1/0",
                    "type": "part",
                    "descriptor": <snipped>,
                    "config": <snipped>
            "name": "1"

The path should have been:

"path": "1/0",

This is a problem because L1 does not know anything about P2, and I’m trying to do a full render of L1 in L1…

I feel like I’m implementing a ugly hack to work around the fact thymeleaf.render does not do a full render.

Perhaps I can use portal.componentUrl?!/module-lib_xp_portal.html#.componentUrl

I have an idea for another solution that should work.

In P1 instead of using

content.get({ key: })

I can do

httpClient.request({ url: portal.pageUrl({ id: }) });

That should give me a fully rendered P2, right?

I dunno, but I suspect running a request is slower than calling a controller directly?

Then I’ll pass along some request params to say which view I want.
Default view is full page, but in this case I only want the region.
Yup that should work.

Getting on the train home I realize my mistake. I was assuming that component paths where relative to the region, when in fact they are relative to the page. So as long as I do all the “postProcessing” in the page controller rather in the layout controller, it should work out.

I have now moved from java.lang.NullPointerException to java.lang.StackOverflowError :slight_smile: hehe

Here is some of the code I ended up with:

const regexString        = '<!--# COMPONENT (.+?) -->';
const firstMatchRegex    = new RegExp(regexString);
const globalReplaceRegex = new RegExp(regexString, 'g');

export default class pageController {

    processPart(component) {
        const partName = Component.getName(component);
        switch (partName) {
            case 'partName1':
            case 'partName2':
                return Component.getController(component).get(this.request, { config: component.config }).body;
                throw new Error(`${pageName} page processPart does not support part component:${toStr(component)}`);
        } // switch partName

    processText(component) {
        return `<section data-portal-component-type="text">${component.text}</section>`;

    processLayout(component) {
        const layoutName = Component.getName(component);
        switch (layoutName) {
            case 'layoutname':
                return Component.getController(component).get(this.request, { component }).body;
                throw new Error(`${pageName} page processLayout does not support layout component:${toStr(component)}`);
        } // switch layoutName

    replaceComponentComments(htmlString) {
        while(htmlString.match(firstMatchRegex)) {
            htmlString = htmlString.replace(globalReplaceRegex, (match, path, offset, string) => {
                const component = Component.getComponentWithPathInPage(path,;
                if (Component.isPart(  component)) { return this.processPart(  component); }
                if (Component.isText(  component)) { return this.processText(  component); }
                if (Component.isLayout(component)) { return this.processLayout(component); }
                throw new Error(`${pageName} page replaceComponentComments does not support component:${toStr(component)}`);
            }); // replace
        } // while firstMatchRegex
        return htmlString;


We’re a bit unsure where you get the problem with rendering components? Are you trying to render a part from another page in error.js?

I have sort of made my own version of fragment (by using a page) which I call snippet.
The benefit of my version is that it can be edited WYSIWYG.

So actually the problem occurs when you render a page, from a page.
Either way replaceComponentComments works and I no longer have any problems.

It might render slower though, hmm my snippet are typically static, so I could cache them.

Hmm fragments can be edited (corrected) wysiwyg too?

But can you edit it WYSIWYG?

Can you create a page template for a fragment of some part or layout?

Not sure what you mean here? A fragment can definetly be edited wysiwyg - be sure to configure mappings so it renders with contextual design. See how this is done here:

1 Like

Interesting. We definetly have to try that out. Feels a bit like a “hidden” feature, in that you gotta know a lot before you figure that one out.

Guess it could be clearer - but it’s definetly documented:

A benefit that my snippet has over fragments is that it can contain multiple layouts.
Since it’s simply region it can contain just about anything.

So I can have only 1 content selector in site.xml, and edit the snippet as I like afterwards.

With fragments I could get away with a single fragment selector, with maximum=“0”.
And add the fragments in order in the page controller/view.

But if I wanted to add layout fragments, I would have to edit the site configuration,
rather than simply editing the snippet.

We are planning a “container” component type that will potentially address this issue too in the future