Always allow an optional trailing slash at the end of each URL. Both the URL '.../groups' and '.../groups/' are valid, and will trigger the same route.
Split the 'groups' route into two seperate routes. One without a 'page' parameter and another including the 'page' parameter. I will have to think about how to implement optional parameters, it would definitely be a nice feature.
A ModuleFactory was created inside the ModuleFactory to load all the seperate modules. Of course, this is not necessary and is replaced by a call to '$this'.
A router shouldn't know where his routes come from, that would give the router to much responsibility and reduce the reusability of the route.

The creation of the routes already was the responsibility of each module. Now, a module manager will collect all modules an provide the routes when needed.

The router however won't even know that a module manager is providing his routes. The application creates the module manager and passes the routes to the router. This could still be considered the responsibility of the application.
Extracted the creation of the router and page factory in the application to their own methods. This way, a custom application can possibly override that functionality.
The module manager shouldn't know where his modules are coming from, it should only manage the modules given to him. The creation of the modules was already done by the module factory. The application now creates those modules via the module factory and passes them to the module manager.
Moved the creation of the router and page factory to the constructor of the application. They will be used anyway, so why not create them as early as possible. Any errors will now surface sooner, rather than later.
Renamed the 'regex' and 'extractUrlData' methods in the route class to 'createRegex' and 'extractDataFrom'. This conveys the meaning of the methods better and is more readable.
A request should know how to create the right page based on its URL. Before, the application would coördinate this proces, now the request does this himself. The necessary router and page factory are still created by the application and passed to the request.
Removed the unused 'dispatch' method inside the application.