Breaking content directory down by year/month to improve performance

There is something wrong with your routes, the second one should have the full path that you don’t want to use.

While the first should be shows/(:any) and find the page.

As regards the model, make sure you actually get the desired result, the first slash might be too much of a good thing.

I keep getting delivered the error page in the first route.

 'pattern' => 'shows/(:any)',
            'action'  => function ($uid) {

                $page = page('shows')->children()->children()->children()->find('shows/' . $uid);

                // $page = page($uid);

                if (!$page) $page = page('shows')->children()->children()->children()->find('shows/' . $uid);
                if (!$page) $page = site()->errorPage();

                // var_dump($page, $uid);
                // exit();


                return site()->visit($page);
            },
            'pattern' => 'shows/(:num)/(:num)/(:all)',
            'action'  => function ($year, $month, $all) {
                go('http://localhost:3000/shows/' . $all);
            }

I don’t think I have everything I need in the first pattern in order to grab the page data. I need access to to the year and month values…

Should be

$page = page('show')->children()->children()->children()->find($uid);

Or

$page = page('show')->index()->find($uid);

Plus you are using the same code twice

In the second route, don’t hardcode the domain, redirecting to shows/ . $all is enough.

go('shows/' . $all);

The general problem with this approach is that you have to make sure your show slugs are unique, otherwise this whole setup with removing the years and months will not work out. And there is no built-in mechanism that checks if any one of the siblings descendants has the same slug. So if you want to go down this route, you need a way to make sure this cannot happen (e.g. with a page.create:before hook).

Hmm

Still not working.

$page = page('show')->index()->find($uid);

if (!$page) $page = page('show')->index()->find($uid);
if (!$page) $page = site()->errorPage();

Are you saying I don’t need the first line?

I think there may be any issue as my uid within the content structure looks to be show name with the year and month prepended. Not only the name of the show ( which is what the $uid gives me) So when using the uid to retrieve there is no folder to match.

$page = page('shows')->index()->find($uid);
var_dump($page, $uid)

i.e that outputs the following

NULL string(35) "silver-waves-15th-september-20.json"

And if I dump out $page->id() in my show.json.php file I get the following…

string(44) "shows/2020/09/silver-waves-15th-september-20"

No. But these are exactly the same apart from the if statement, so you first define $page and if it’s not a page, you do the same again, which is superflous-

But if uid contains .json, then it will never find that page, because the extension is not part of the slug. As I already wrote above, you have to render the page as json, like here: Breaking content directory down by year/month to improve performance - #14 by texnixe
So something like this:

 'pattern' => 'shows/(:any).json',
 'action'  => function ($uid) {
   if ( $page = page('shows')->index()->find($uid) ) {
         return $page->render( [], 'application/json' );
    }

    return site()->errorPage();
}

I recently (finally!) got round to upgrading to Kirby 3, and, realising that I had some 700 entries in the notes folder on my site, thought that it would be a good moment break it down a little in the manner described above. I’ve chosen to only group the posts by year, making it a little simpler, but, having tried all of the methods suggested above, could only get it to work by ‘hard wiring’ the second part of the routing, e.g.:

'routes' => [
    [
        'pattern' => 'notes/(:num)/(:any)',
        'action'  => function($year, $any) {
            return go('notes/' . $any);
        }
    ],
    [
        'pattern' => 'notes/(:any)',
        'action'  => function($uid) {
            $page = page($uid);
            if(!$page) $page = page('notes/' . '2021/' . $uid);
            if(!$page) $page = page('notes/' . '2020/' . $uid);
            // etc. 
            if(!$page) $page = site()->errorPage();
            return $page; 
        }
    ],
  ],  

And I also had to hardwire the year into the my notes section blueprint for the panel.

parent: kirby.page("notes/2021")

It all works fine, but I was wondering if there was a better way of doing it – I couldn’t get any of the suggestions in the thread above to work. I could get the examples (elsewhere on the forum and on the Kirby site) for omitting a ‘notes’ or ‘blog’ folder to work, but not when moving one step down to one of their children.

Also one thing that I noticed is that $page->url() still gives me the full path including the ‘hidden’ year folder – which means that I now have double URLs on my posts unless I set up a way of dancing around that.

Any suggestions on better ways to tackle the above?

For the route it would make sense to pluck all existing year folder into an array, then to loop through this year array, to find the page.

// get an array of years from the year folder slugs
$years = page('notes')->children()->pluck('slug', ',');

foreach ( $years as $year ){
  if ( $page =  page('notes/' . $year . '/' . $uid) ) {
    return $page;
  }
  return site()->errorPage();

What exactly do you want to achieve in the Panel?

In a pages section, you cannot have multiple parents.

OK, thanks, I’ll try that.

In the panel I simply want to be able to access all the notes, but importantly to also be able to add a new note directly into the folder of the current year – 2021, for example.

Ok, tried the above, and it gets me the the desired URL, but the site throws up an error page.

What do you mean?

Simply that /notes/2021/my-post gets routed to /notes/my-post, but the error page is displayed. I can see the re-routed URL in the bowser, but with my error page displayed.

Ok, i’m not quite sure what the issue is, but this works;

  'routes' => [
 
    [
        'pattern' => 'notes/(:any)',
        'action'  => function($uid) {

            $years = page('notes')->children()->pluck('slug', ',');

            foreach ( $years as $year ){
              if ( $page =  page('notes/' . $year . '/' . $uid) ) {
                return $page;
              }
            }
            $this->next();
      }

    ],
    [
      'pattern' => 'notes/(:num)/(:any)',
      'action'  => function($year, $any) {
          return go('notes/' . $any);
      }
    ],
  ], 

As for the Panel, I’d suggest you use a plugin (Pagesdisplay section) to display all notes from all years, and a separate section for drafts where you can add new notes to the current year. Make this parent dynamic via a page method.

It does work! Thank you once again for your (as always) quick replies :grinning: .

I’ll try your suggestions for the panel.

Do you have any suggestions on how to tackle $page->url() still showing the full route that includes the year folder. Is there a best practice when it comes to something like that in the situation above?

Oh, sorry, forgot about that. A page model where you overwrite the URL method would be the way to go: Page models | Kirby CMS

Great, thanks!

Ok, so for permalinks I can simply exclude the year folder using page->slug rather than page->url, and use href="notes/<?php echo $article->slug() ?>" for the collected notes overview, but then when using categories or tags I get a double notes folder eg. site/notes/notes/my-post – so how to get around that?

Or how exactly were you thinking I should override the URL method?

You should override the URL in a model so that it contains the target url, i.e. the URL of the grandparent (= notes page) plus the slug of the article

 public function url($options = null): string {
    return page('notes')->url() . '/' . $this->slug();
  }

The model must be created for the article page (i.e. have the name of the blueprint you use for those articles)

Wonderful. All the pieces finally in place. Thank you.