In order to solve this in a pragmatic and maybe not that elegant way, I came up with the following. First I connected the page named parameter:
Router::connectNamed(array('page' => '[\d]+'), array(
'default' => false,
'greedy' => false
));
according to the cookbook, this will only enable the page named parameter and disable all others, and it will only accept numerical values.
I am not sure whether this was particularly connected to my specific issue though.
Secondly, I reread the cookbook and saw here that the order of connections in routes.php really matters. I.e., when an url has to be routed, connections at the top of the file have higher priority over connections at the bottom. Thus, I came up with this configuration order:
// view all posts by year and month
Router::connect('/blog/:year/:month/*', array(
'controller' => 'posts',
'action' => 'index'
), array(
'year' => '[12][0-9]{3}',
'month' => '0[1-9]|1[012]'
));
// view all posts by year
Router::connect('/blog/:year/*', array(
'controller' => 'posts',
'action' => 'index'
), array('year' => '[12][0-9]{3}'));
// view all posts
Router::connect('/blog/*', array(
'controller' => 'posts',
'action' => 'index'
));
Before, it was reversed, i.e. /blog/* was connected first. Because of the greedy star, this "swallowed" everything, also stuff like /blog/2012/, where 2012 was just passed as an argument. Whereas now, I can come up with /blog/2012/page:2, /blog/2012/05/page:2, and "fake" urls like /blog/2012/5ssfd/page:2 will map to /blog/2012/page:2, i.e. in this case, the first connection wasn't matched, so it jumps to the second connection. The reason I did it this way is that I wasn't able to do stuff like /blog/:year/:month/page:page and thus avoid the greedy star (maybe somebody knows how to do this).