diff --git a/district/migrations/0065_alter_districtgeofeaturedetailpage_sort_order.py b/district/migrations/0065_alter_districtgeofeaturedetailpage_sort_order.py
new file mode 100644
index 0000000000000000000000000000000000000000..444ee744078a3c60e6e4542a2eaa825a1b4042ac
--- /dev/null
+++ b/district/migrations/0065_alter_districtgeofeaturedetailpage_sort_order.py
@@ -0,0 +1,24 @@
+# Generated by Django 4.0.3 on 2022-05-03 05:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("district", "0064_alter_districtgeofeaturedetailpage_options_and_more"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="districtgeofeaturedetailpage",
+            name="sort_order",
+            field=models.IntegerField(
+                blank=True,
+                default=0,
+                help_text="Čím větší hodnotu zadáte, tím později ve výpise položek bude. Můžete tím tedy ovlivňovat očíslování položky ve výpisu.",
+                null=True,
+                verbose_name="Index řazení",
+            ),
+        ),
+    ]
diff --git a/district/models.py b/district/models.py
index d7a17f6535302b828033257cd9166f65e5368e07..9da207a351f96a93759085eb62669227257e84c3 100644
--- a/district/models.py
+++ b/district/models.py
@@ -1,9 +1,11 @@
 import json
 import random
 
+from django.core.cache import cache
 from django.core.exceptions import ValidationError
 from django.core.paginator import Paginator
 from django.db import models
+from django.db.models import signals
 from django.shortcuts import render
 from django.utils.translation import gettext_lazy
 from modelcluster.contrib.taggit import ClusterTaggableManager
@@ -1178,7 +1180,13 @@ class DistrictGeoFeatureCollectionPage(
         verbose_name = "Stránka s mapovou kolekcí"
 
     def get_features_by_category(self):
-        features = self.get_children().live().specific().select_related("category")
+        features = (
+            self.get_children()
+            .live()
+            .specific()
+            .prefetch_related("category")
+            .order_by("districtgeofeaturedetailpage__sort_order")
+        )
         categories = sorted(set(f.category for f in features), key=lambda c: c.name)
 
         return [
@@ -1244,6 +1252,10 @@ class DistrictGeoFeatureCollectionCategory(Orderable):
         return f"{self.page} / {self.name}"
 
 
+def make_feature_index_cache_key(feature: "DistrictGeoFeatureDetailPage"):
+    return f"DistrictGeoFeatureDetailPage::{feature.id}::index"
+
+
 class DistrictGeoFeatureDetailPage(
     ExtendedMetadataPageMixin, MetadataPageMixin, SubpageMixin, Page, Orderable
 ):
@@ -1286,7 +1298,9 @@ class DistrictGeoFeatureDetailPage(
         "Index řazení",
         null=True,
         blank=True,
-        help_text="Čím větší hodnotu zadáte, tím později ve výpise položk abude.",
+        help_text="Čím větší hodnotu zadáte, tím později ve výpise položek bude. Můžete tím tedy ovlivňovat očíslování "
+        "položky ve výpisu.",
+        default=0,
     )
     sort_order_field = "sort_order"
 
@@ -1322,14 +1336,21 @@ class DistrictGeoFeatureDetailPage(
 
     @property
     def index(self):
-        if not hasattr(self, "__index"):
-            self.__index = (
+        key = make_feature_index_cache_key(self)
+        cached_index = cache.get(key)
+
+        if cached_index is None:
+            cached_index = (
                 list(
-                    self.get_siblings(inclusive=True).values_list("pk", flat=True)
+                    self.get_siblings(inclusive=True)
+                    .order_by("districtgeofeaturedetailpage__sort_order")
+                    .values_list("pk", flat=True)
                 ).index(self.pk)
                 + 1
             )
-        return self.__index
+            cache.set(key, cached_index)
+
+        return cached_index
 
     def get_meta_image(self):
         return self.image
@@ -1369,3 +1390,18 @@ class DistrictGeoFeatureDetailPage(
             )
         except ValueError as exc:
             raise ValidationError({"geojson": str(exc)}) from exc
+
+
+def invalidate_feature_index_cache(sender, **kwargs):
+    """When DistrictGeoFeatureDetailPage changes, delete all sibling index cache keys to force recompute."""
+    if kwargs.get("instance"):
+        keys = [
+            make_feature_index_cache_key(feature)
+            for feature in kwargs["instance"].get_siblings(inclusive=True)
+        ]
+        cache.delete_many(keys)
+
+
+signals.post_save.connect(
+    invalidate_feature_index_cache, sender=DistrictGeoFeatureDetailPage
+)