Caching not working when using static::render() in route definition

I’m using a static render method in a route definition to render some JSON from a page template and pass it into the home template.

$data = array();
$data['initData'] = array(
        'collection' => static::render($collection), // Render the start collection template JSON (site/templates/tour-dates.php)
        'dates' => static::render($dates), // Render the Tour Dates template JSON (site/templates/tour-dates.php)
        'slug' => $slug,
      );

return array('home', $data); // return the home page, passing in the dates and collection and initial slug data (site/templates/home.php);

This is working perfectly — when I visit the site, the homepage template is rendered, and the data is passed into the template.

However when I turn on caching (c::set('cache', true);), the collection page is rendered and returned and cached, rather than the homepage. (It will always return at the first static::render() it finds — so if I move dates above collections, it will return the dates page JSON.

Not sure why you need to pass JSON to a template, or where the static::render method is defined in relation to a route definition. If you need to do some extra grooming of information before you get to the template, this sounds like something a controller should do.

Otherwise, you ought to leave the data as a standard PHP object/array until just before you need to json_encode it.

If this is the body of your route action function, it’d probably be better to assemble all the data you need right there (or use page models!), then pass that, rather than attempting to render a JSON string from a PHP template.

If you’d like some more guidance, would you mind posting more information about your use case? I have a feeling we can figure out a really elegant solution with not much work.

Thanks August!

The reason is, I’m using Kirby as a CMS for a JSON API. So any request will return the home template, with the JSON of the other pages (for example the dates page) saved to the window object.

The pages JSON also need to be available by an API endpoint (e.g. localhost:3000/api/dates), which is why I output JSON in the templates, and static::render the ones required on first load, saved to the window.

I’m 90% front-end, 90% javascript developer, and pretty pushed for time — so this is why I went for this approach, as it’s as much as I really understand when it comes to back-end and PHP. But I’m totally up for suggestions for a better approach!

Included some more code for you below (abbriviated!)

config.php

c::set('routes', array(
  array(
    'pattern' => array('(:any)', '/'),
    'action'  => function($slug) {
      $dates = page('dates');
      $data = array();
      $data['initData'] = array(
        'dates' => static::render($dates),
        'slug' => $slug,
      );

      return array('home', $data);
    }
  ),

  // api routes to render JSON response
  array(
    'pattern' => array('api/(:any)'),
    'action'  => function($slug) {
      // API stuff in here...
    }
  )
));

home.php

<?php snippet('header') ?>

<script type='text/javascript'>
	window.tourDates = <?php echo $initData['dates'] ?>;
	window.initSlug = '<?php echo $initData['slug'] ?>';
</script>

<?php snippet('footer') ?>

dates.php

<?php
	$dates = $page->dates()->toStructure();
	$json = array();

	foreach ($dates as $date) {
		$dateJson = array(
			'date' => (int)$date->date(),
			'venue' => (string)$date->venue(),
			'link' => (string)$date->link(),
			'mapslink' => (string)$date->mapslink(),
			'canceled' => (boolean)$date->canceled()->bool(),
		);
		array_push($json, $dateJson);
	}

	echo json_encode($json);
?>

Kirby 2.4.0 will have content representations. The feature is already available on Kirby’s develop branch. Maybe this native solution fits your needs. It’s also compatible with the cache. :slight_smile:

Ah that looks awesome… but unfortunately I can’t use a pre production feature for this project.

Do you have any other suggestions - appreciate the help.

Overall, I think it’s better to try and use Kirby’s built-in JSON response helpers than to echo it into a template. If you’re building an API to output some information that your own site needs to consume, you should consume it the same way another resource might— via the API, not a shortcut… :wink:

Any time I need data as JSON in a template, I set up a route for an AJAX request.

On the server:

c::set('routes', [
    'pattern' => 'json/gobbledygook',
    'action' => function () {
      # ... Gather your data, and pass it to `response::json()`
      # Sometimes, you may want to create a little plugin
      # to help assemble complex requests.
      return response::json(['key' => 'val']);
    }
  ]);

In the browser:

$.ajax('/json/gobbledygook', {
  success: function (data) { console.log(data); } // { 'key': 'val' }
});

Theoretically, you can fetch it and initialize whatever other functions/libraries depend on it.

I’ll update this post with more thoughts, as they come to me…