Clean URL pagination (rewrite ?page=parameter)
You should write a PathProcessor that implements:


Drupal\Core\PathProcessor\InboundPathProcessorInterface
Drupal\Core\PathProcessor\OutboundPathProcessorInterface


Example:

file: modules/custom/mymodule/src/PathProcessor/MymodulePathProcessor.php

namespace Drupal\mymodule\PathProcessor;

use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\Render\BubbleableMetadata;

class MymodulePathProcessor implements InboundPathProcessorInterface, OutboundPathProcessorInterface {

  public function processInbound($path, Request $request) {
    if (strpos($path, '/my-page-') !== 0) {
       return $path;
    }
    $page =  preg_replace("/^\/my-page-(\d+)/", '$1', $path);
      if (ctype_digit($page)) {
        \Drupal::request()-&gt;query-&gt;set('page', $page);
        return "/my-page";
    }
    else {
      \Drupal::request()-&gt;query-&gt;set('page', 0);
      return "/my-page";
    }
    return $path;
  }

  public function processOutbound($path, &amp;$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
    if ($path === '/my-page' &amp;&amp; isset($options['query']['page'])) {
      $page = $options['query']['page'];
      unset($options['query']['page']);
      return '/my-page-' . $page;
    }
    return $path;
  }

}


file: modules/custom/mymodule/mymodule.services.yml

services:
  mymodule.path_processor:
    class: Drupal\mymodule\PathProcessor\MymodulePathProcessor
    tags:
    - { name: path_processor_outbound}
    - { name: path_processor_inbound}


We also need to do a custom alteration to the pager. For the full pager the links are not created as the usual link render elements, so they don't go through the processOutboud, instead the twig file (classy/templates/navigation/pager.html.twig) uses a custom format. The next hook function does the adjustment.

file: modules/custom/mymodule/mymodule.module

/**
 * Implements hook_preprocess_HOOK().
 * Implements hook_preprocess_pager().
 *
 * @param $variables
 */
function mymodule_preprocess_pager(&amp;$variables) {
  $current_path = \Drupal::service('path.current')-&gt;getPath();
  if ($current_path === '/my-page')
  foreach ($variables['items']['pages'] as &amp;$page_link) {
    $page =  preg_replace("/^\?page=(\d+)/", '$1', $page_link['href']);
    $page_link['href'] = 'my-page-' . $page;
  }
}