I’d recommend caching – generating the HTML mark-up and storing that in a cache, and using that rather then generating the list on each page load.
Of course, the cache would have to cleared if a recipe is ever added, removed or changes title.
add_shortcode('recipe_list', 'recipe_query');
function recipe_query( $atts, $content ){
//Get mark-up from cache (if present)
$html = get_transient( 'wpse123038_recipe_list' );
if( !$html ){
//Nothing in cache - generate mark-up
$html="";
....
//Store in cache (for a long time - a year?)
set_transient( 'wpse123038_recipe_list', $html, 60*60*24*365 );
}
return $html;
}
Now everytime we add/remove a post (or may be its published and then we set it to ‘draft’) – basically anytime it might go from not being in the list, to being on the list :). (Note, you’ll also want to clear the cache whenever the recipe title changes…)
function wpse123038_recipe_status_changes( $new_status, $old_status, $post ) {
//Check if status has changed
if( $new_status == $old_status ){
return;
}
//Check post type
if( 'recipes' != get_post_type( $post) ){
return;
}
//Check if we're publishing on 'unpublishing' it.
if( 'publish' == $new_status || 'publish' == $old_status ){
delete_transient( 'wpse123038_recipe_list' );
//For bonus points, regenerate the cache here :)
}
}
add_action( 'transition_post_status', 'wpse123038_recipe_status_changes', 10, 3 );
So when ever a recipe is published, or is ‘unpublished’ the cache is cleared. This means the next visitor will have to wait for the mark-up to be generated again.
Of course, we’re usually less concerned about page load times admin-side, so when the cache is cleared, you could regenerate it then. Giving a (usually) seamless experience on the front-end.