.json template returning regular template

I’m following the cookbook recipe to add a load more button on a blog page. I’ve done this before on another site, but wanted to use the updated version.

The problem is, ‘/blog.json’ is returning the same as ‘/blog’ despite all of the templates and controllers being setup correctly (it’s just loading the blog page as normal). What could be causing this? There’s nothing unusual about my Kirby setup.

Hm, I’d say you have an error somewhere. Have you checked that you have created all the right files in the right locations with the right filenames?

Here is my setup. Nothing special about it really. I’m just really puzzled as to why the .json isn’t displaying at all, even if I change the data to a string instead of the snippet.

templates/blog.php

<section class="row mb">
    <ul class="row blog-articles" data-page="<?= $pagination->nextPage() ?>">
        <?php foreach($articles as $article): ?>
        <?php snippet('blog-article', ['article' => $article ]) ?>
        <?php endforeach ?>
    </ul>
    <div class="cta-container">
        <button class="cta load-more">Load More</button>
    </div>
</section>

controllers/blog.php

<?php

return function ($page) {
  $limit    = 3;
  $articles = $page->children()->listed()->flip()->paginate($limit);
  
  return [
      'limit'      => $limit,
      'articles'   => $articles,  
      'pagination' => $articles->pagination(),  
    ];
};

templates/blog.json.php

<?php

foreach($articles as $article) {
  
  $html .= snippet('blog-article', ['article' => $article], true);
  
}
$json['html'] = $html;
$json['more'] = $more;
  
echo json_encode($json);

controllers/blog.json.php

<?php

return function ($page) {

  $limit      = 3;
  $articles   = $page->children()->listed()->flip()->paginate($limit);
  $pagination = $articles->pagination();
  $more       = $pagination->hasNextPage();

  return [
      'articles' => $articles,
      'more'     => $more,
      'html'     => '',
      'json'     => [],
    ];
};

snippets/blog-article.php

<li class="col-xs-12 col-md-4 article">
    <div class="article__image">
        <?php if ($image = $article->cover()->toFile()): ?>
        <a href="<?= $article->url() ?>">
            <div class="r r-16-10">
                <img class="br" src="<?= $image->url() ?>" alt="<?= $image->alt()->html() ?>" />
            </div>
        </a>
        <?php endif ?>
    </div>
    <div class="article__text">
        <div class="meta">
            <span class="meta"><?= $article->date()->toDate('d M Y') ?></span>
            <span class="meta"><?= $article->authorName() ?></span>
        </div>
        <h3 class="h5"><a href="<?= $article->url() ?>"><?= $article->title()->text() ?></a>
        </h3>
        <ul class="article__tags">
            <?php foreach ($article->tags()->split() as $tag): ?>
            <li class="meta"><?= $tag ?></li>
            <?php endforeach ?>
        </ul>
    </div>
</li>

LGTM. No idea what could be going wrong unless you have set up a redirect somewhere in a route or .htaccess. Maybe a route to remove the blog part from articles or something like that?

Ah yes, a route I have dealing with landing pages is causing the issue:

        [
            'pattern' => '(:any)',
            'action'  => function($uid) {
                $page = page($uid);
                if(!$page) $page = kirby()->page('landing-pages/' . $uid);
                if(!$page) $page = site()->errorPage();
                return site()->visit($page);
            }
        ],

I found this on the Routng Guide:

When using placeholders like (:any) and (:all) or regex patterns, make sure that they are not too greedy and you accidentally catch routes you want left intact, resulting in things not working as expected anymore.

Example: A pattern like (:any) will not only catch routes with a URL part like notes or photography , but also their JSON content representations notes.json or photography.json . In this case, using (:alphanum) can be more suitable.

:alphanum doesn’t work in my case though as some of the URLs I need to catch have hyphens in them. What’s the best way to omit the blog page from this route?

You can use a regex pattern instead, after all (:any) etc. are nothing else but wildcard patterns.

These are the used wildcard patterns:

    protected $wildcards = [
        'required' => [
            '(:num)'      => '(-?[0-9]+)',
            '(:alpha)'    => '([a-zA-Z]+)',
            '(:alphanum)' => '([a-zA-Z0-9]+)',
            '(:any)'      => '([a-zA-Z0-9\.\-_%= \+\@\(\)]+)',
            '(:all)'      => '(.*)',
        ],
        'optional' => [
            '/(:num?)'      => '(?:/(-?[0-9]+)',
            '/(:alpha?)'    => '(?:/([a-zA-Z]+)',
            '/(:alphanum?)' => '(?:/([a-zA-Z0-9]+)',
            '/(:any?)'      => '(?:/([a-zA-Z0-9\.\-_%= \+\@\(\)]+)',
            '/(:all?)'      => '(?:/(.*)',
        ],
    ];

So you could extend the alphanum pattern with a hyphen, for example.

1 Like

Thanks, I’ll look into it.

Guten Rutsch @texnixe!