Get random post

Over the weekend I created a new feature on CodeTips that will grab a random post on every page refresh, and display it as a “featured post”.

If you scroll down to the bottom of the home page you should see the new section. If you refresh the page, you should get a different article each time.

Not sure if anybody else could make use of this feature? Happy to walk you through implementing it if so.

1 Like

Thats a very nice feature maybe you can post the code so if anyone is also intersted (im very intersted)

@kmutahar - sure.

You first need to create a custom handlebars helper, in the helpers directory (current/core/server/helpers/).

  • Create a new file called randomElement.js and put the following code into it. Note: this is very messy at the moment, but I plan on cleaning it up and allowing a number parameter to be passed through for the number of random posts required.
module.exports = function randomElements(array, options) {
  // Shuffle array
  const shuffled = array.sort(() => 0.5 - Math.random());

  // Get sub-array of first n elements after shuffled
  const selected = shuffled.slice(0, 1);

  let result = ''; 
  
  selected.forEach( (item) => { result = result + options.fn(item) });
  
  return result;

}
  • In the same directory, edit the index.js file and add the following lines (it should be clear where to put them).
coreHelpers.randomElement = require('./randomElement');

registerThemeHelper('randomElement', coreHelpers.randomElement);
  • Then amend the list of supported helpers, so it doesn’t fail when you try to upload your theme. Update the gscan library (node_modules/gscan/lib/specs/v1.js) and add 'randomElement' into the knownHelpers array.

  • Then use it in your theme to get a random post:

    {{#get "posts" limit="all"}}
      {{#if posts}}
        {{#randomElement posts}}
          {{> post-card}}
        {{/randomElement}}
      {{/if}}
    {{/get}}

@Kevin - do you guys ever accept custom helpers into the core package? If I cleaned the code up - would you accept a PR?

2 Likes

Ill test it out later. and im guessing that since you are editing the core files that after any update you have to remake all the edit again.

and honestly this would be an amazing feature

Oki doki, let me know if you need anything clarified! Or if it just works and you feel like letting me know :slight_smile:

Unfortunately so, unless the ghost team accepts it into the core package. If it works for you, I can clean up the code more and raise a PR so it’s in there by default.

Thanks :upside_down_face::upside_down_face::upside_down_face:

Very rarely, there should be as little logic in the core helpers as possible. In this case your custom helper won’t work for the vast majority of setups which use caching in front of a Ghost instance - the first request will get a random entry and that will be cached so every subsequent request will show the same entry. You need to think of Ghost generating static pages rather than dynamic ones, if you need dynamic content for each request then it should be done client-side.

I’m not saying you’re wrong (you guys obviously know your product better than I do), but I’m pretty sure the request is being done client-side (i.e. the posts GET request), and then the posts array is just passed through to the helper than does some simple JS to pull out random entries.

The helper doesn’t actually do any requests.

Does that make sense? I’m sure the helper isn’t what you’d expect, it was just something hacky to get what I wanted.

@omisnomis all helpers are server-side rendering helpers, in this case I’m talking about page requests.

  1. 1st visitor request comes in - server side renderer runs “random” helper and selects a post, response is cached (potentially forever until another server-side event triggers a cache clear)
  2. all subsequent visitor requests are served from the cache, the “random” helper is never run so it never really appears to be random, everyone will see the same post selected in 1 even when refreshing

Even though there is a request being made to posts?

Is there a setting to enable cache on Ghost, because I’m not seeing this behaviour?

More importantly - I assume you’re recommending that a script tag is included, that makes the GET request and updates the page through JS rather than a custom helper?

After the first request, subsequent requests can stop at the cache server layer so there would be no request being made to posts as seen by the Ghost instance.

Is there a setting to enable cache on Ghost, because I’m not seeing this behaviour?

No, I’m describing a typical setup for a production Ghost blog as well as Ghost(Pro). This is one of the big problems with introducing this type of helper, it will have very different and unexpected behaviour depending on the external environment and configuration.

I assume you’re recommending that a script tag is included, that makes the GET request and updates the page through JS rather than a custom helper?

Exactly.

Ah, I see! Thanks for taking the time to explain all of that. I did create it for my use case, I didn’t give much thought into everything else :slight_smile:

I’ll do this in the near future, and I’ll update this article with those steps instead. At least that way it doesn’t require redoing after a new release :+1:

My only concern with doing this client side is it would mean exposing my Content API Key, right?

Yes, Content API Keys are designed to be exposed, they only give access to what is already public data.

Fair enough :+1: - thanks again! I’ll work on the new way this weekend.

Will share the new method when completed so nobody screws up their site because of the first method I posted.

@kmutahar - it might be worth you waiting, and I’ll come up with an improved solution you can use.

@Kevin - follow up question, if I’m using the script method I don’t have access to the img_url helper. Is this available outside of handlebars?

@omisnomis all helpers are handlebars-only, although if they are generic enough you may be able to find underlying functions extracted to utility functions in the Ghost-SDK mono repo.

However, img_url is not an extracted function because its base functionality is already part of the Content API. Eg, if a post object has a feature image then it will be present in the API response as "feature_image": "https://yoursite.com/content/images/...". What is it that you wanted to use the helper for?

Hey @Kevin - I thought as much.

I mainly wanted to use the helper for the srcset (e.g. below), and I was being lazy and not figuring out how to do it without :upside_down_face:. I’ll have a look into how the img_url figures out the srcset attribute and stop being lazy :grin:

<img
                    srcset="{{img_url feature_image size="s"}} 300w,
                            {{img_url feature_image size="m"}} 600w,
                            {{img_url feature_image size="l"}} 1000w,
                            {{img_url feature_image size="xl"}} 2000w"
                    sizes="(max-width: 800px) 400px,
                            (max-width: 1170px) 700px,
                            1400px"
                    src="{{img_url feature_image size="xl"}}"
                    alt="{{title}}"
                />

Check out my PR for a simple solution to getting N random posts:

https://github.com/TryGhost/Ghost/pull/11130

It only requires changing two lines in core/frontend/helpers/foreach.js Then you can get 6 random posts with:

{{#get "posts" limit="all"}}
  {{#foreach posts random=1 limit=6}}
    . . .
  {{/foreach}}
{{/get}}

@jcoffland this is not the correct way to do this. See above, the helpers should not be changed.

Instead, the content API should be used.

It’s far easier, cleaner and you don’t mess with the Ghost core. Unless this is added into the official codebase, this should not be used.