While writing this answer I found the solution in the documents on hackage.
There is a function called nullDir
which you use for when the path is empty.
Using this function my example routes would look like this:
1 routes :: ServerPart Response
2 routes = msum
3 [ dirs "pages/delete" $ delete
4 , dirs "pages/edit" $ edit
5 , dirs "pages/save" $ save
6 , dirs "pages" $ listPages
7 , nullDir >> indexPage
8 , 404Page]
Now the index page
is only shown if the path is empty, and the 404 page
is shown for all other fall throughs.