Very Slow Page Load When Listing Basic Links

Hello,

I have the following on a page listing out car posts:


<section>
<h2>Cars</h2>
<div>
  <ul>
    {{#foreach posts}}
    {{#has tag="car"}}
    <li>
        <a href="{{url}}">{{title}}</a>
    </li>
    {{/has}}
    {{/foreach}}
  </ul>
</div>
</section>

and it is taking 60 seconds to load the page. This is on a brand new install using digital ocean droplet and update to the latest ghost. All other pages are fine and load quickly. No errors in console.

There are 58 links to be outputted here.

Why might the above take so long to load just 58 a tags? Any help you may provide to help me understand what I need to change is greatly appreciated.

Michelle

How many posts are there?

How do posts get to that page? If there’s a #get request, then perhaps add the filter for tags there, so that there are fewer posts to loop over.

How big is the droplet? You might need to give MySQL some more swap space? There was a forum thread a while back on improving MySQL behavior in memory-constrained setups…

Hi Cathy, thank you for your help.

Droplet has: 2 GB Memory / 50 GB Disk

There are 58 posts with the cars tag.

I have this structure in my routes.yml:

  /cars/:
    controller: channel
    template: cars

In my cars template (this pulls into default.hbs) I have:

<section>
<h2>Cars</h2>
<div>
  <ul>
    {{#get "posts" filter="tag:cars"}}
    {{#foreach posts}}
    <li>
        <a href="{{url}}">{{title}}</a>
    </li>
    {{/foreach}}
    {{/get}}
  </ul>
</div>
</section>

To simplify I just have one post now that has a tag of “cars” and yet it takes 6 seconds to load this page with one link.

If relevant, the single post I am now using has just a title and one line of text.

My original 58 posts were imported with title, tags, description, and a mobiledoc field, some of which had quite a bit of content. I was thinking it was related to the mobiledoc field, but this test with a single post and the 6 second delay suggests otherwise.

It might also be worth mentioning from my previous test that the individual car pages with a lot of content loaded very fast, but the list of basic links took 60 seconds.

Not sure where to locate the cause of this slow speed as this should not be taxing my droplet. I’ll keep looking and reply back if I am able to find something and makes this more clear. Thank you again for your help.

How many total posts?

So here is what I have found so far.

When I did the testing with just one post with tag “car” and it took 6 seconds to load, I had 10 other published posts with a different tag (and 1 unpublished post also a different tag - see below - for a total of 17 posts).

The unpublished post with a different tag had 25856 lines of code of html when “beautified” (this was imported in a mobiledoc field). The other ten posts - which were published - also had html, but not that much.

Now when I removed this unpublished post from ghost, the time to load one car post link dropped to about 1.5 seconds (better but still way to slow). This is strange because I don’t understand why a post that has a different tag and is unpublished would have anything to do with the speed of displaying this:

<section>
<h2>Cars</h2>
<div>
  <ul>
    {{#get "posts" filter="tag:cars"}}
    {{#foreach posts}}
    <li>
        <a href="{{url}}">{{title}}</a>
    </li>
    {{/foreach}}
    {{/get}}
  </ul>
</div>
</section>

After having removed that unpublished item, I then tried adding back posts to return to the total of 58 posts tagged “car” and running it, but that just ran for 2 minutes then I got the message: ERR_CONNECTION_CLOSED.

Now each of these 58 posts are imported with a mobiledoc field full of html, not as much as the item I removed. It seems that this is causing the page load time to skyrocket - is ghost reading each of these 58 files completely before just getting the url?

Each of these individual 58 pages load lightening quick if I just visit one of them directly.

Thank you for your assistance.

So normally, when you use a channel, you’d automatically have whatever posts matched the channel filter available in that context. Does this channel have a filter?

You shouldn’t actually need a #get there, given the channel setup. I suspect you’re getting /all/ the posts (from the whole site) sent to that context (although without seeing that routing file its hard to tell), and that object is huge, if you have a lot of posts.

You might try removing that get helper:

  <ul>
    {{#foreach posts}}
    <li>
        <a href="{{url}}">{{title}}</a>
    </li>
    {{/foreach}}
</ul>

And in routes.yaml, set up the controller like this:

  /cars/:
    controller: channel
    template: cars
    filter: tag:cars

Other ideas:
Try starting Ghost with ghost run -D, which causes it to be very very spammy to the console as it runs. With luck, you’ll be able to see something useful when it loads that page. (This is not good for performance, but can be really helpful.) Liberally sprinkling the {{log}} helper into the cars.hbs file may reveal something useful.

Thank you Cathy. I updated the routes and the ul:

routes:
  /:
    controller: channel
    template: home

  /cars/:
    controller: channel
    template: cars
    limit: all   (in this case this is 58 posts total)
    order: title asc
    filter: tag:cars
<section>
<h2>Cars</h2>
<div>
  <ul>
    {{#foreach posts}}
    <li>
        <a href="{{url}}">{{title}}</a>
    </li>
    {{/foreach}}
  </ul>
</div>
</section>

Now when I did this it was still going too slow and I get these messages in the ghost log:

NAME: HelperWarning
CODE: SLOW_GET_HELPER
MESSAGE: {{#get}} helper took 26216ms to complete

or

NAME: HelperWarning
CODE: ABORTED_GET_HELPER
MESSAGE: {{#get}} took longer than 5000ms and was aborted

Next, I imported the same 58 posts but removed the mobiledoc html card so it was empty.

The page with list of car links then loaded instantly.

If I remove and add back say 5 of the car posts but with the mobiledoc html card, then the list of links starts to slow down and the more I add the slower it goes.

What I can do is just create a static page for the links, but what I am finding is that when I import more and more posts with this html content up to the 58 total, the ghost admin pages slow down or even fail to load, so even if I can just create a list of static links, just storing the content seems to delay loading the posts editing page under /ghost/#/posts for example.

I am still exploring the cause, but it seems the mobiledoc html card content - which is simple html with text and image (I will not be able to post this as it is part of paid content) is causing an issue. Imported content is perfect and pages work, but it seems to slow down if I loop over them and just having them (published or unpublished) in posts slows down the /ghost/… admin pages.

Continuing to explore… thank you so much for taking the time to help me with this perplexing issue.

Can you say more about how you’re importing content? I wonder if there’s maybe some malformed content that’d causing be a problem.

The current structure Ghost uses is Lexical, not mobiledoc, but I don’t think support has been removed. Still, interested in hearing more about your process….

Yes - I followed this guide here: Importing content from other platforms to Ghost

and I ran this:

migrate json-html-card my-posts.json

The result is perfect - the posts appear exactly as I need them to appear - just delays when trying to loop over them and delays the /ghost/… admin pages.

You mentioned Lexical so I thought maybe my use of mobiledoc is causing a problem. What I just tried is importing a sample post:

{
  "meta": {
    "exported_on": 1727543446000
  },
  "data": {
    "posts": [
      {
        "id": 1000,
        "title": "Cars",
        "slug": "sample-cars",
        "lexical": "{\"root\":{\"children\":[{\"type\":\"html\",\"html\":\"<div class=\\\"cars\\\"><h1>Welcome</h1><p>This is a sample post.</p></div>\"}]}}",
        "feature_image": null,
        "featured": 0,
        "page": 0,
        "status": "published",
        "published_at": 1727543446000,
        "published_by": 1,
        "meta_title": null,
        "meta_description": null,
        "author_id": 1,
        "created_at": 1727543446000,
        "created_by": 1,
        "updated_at": 1727543446000,
        "updated_by": 1
      }
    ],
    "tags": [],
    "posts_tags": [],
    "pages_tags": [],
    "users": []
  }
}

and I get this error in ghost log:

ERROR Content import was unsuccessful

Does ghost provide documentation for how to follow a similar approach to:

migrate json-html-card my-posts.json

but instead of mobiledoc html card, put html content into the Lexical html child? - not sure what this part looks like for ghost’s editor:

"lexical": "{\"root\":{\"children\":[{\"type\":\"html\",\"html\":

I have only found this sample for the text child:

"lexical":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Hello, beautiful world! 👋\",\"type\":\"extended-text\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"paragraph\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}",

here: Migrating to Ghost - Developer Guide

Thank you again Cathy for mentioning Lexical!