diff --git a/src/main/java/run/halo/app/content/permalinks/CategoryPermalinkPolicy.java b/src/main/java/run/halo/app/content/permalinks/CategoryPermalinkPolicy.java
index d4e363a6c..619d6c89e 100644
--- a/src/main/java/run/halo/app/content/permalinks/CategoryPermalinkPolicy.java
+++ b/src/main/java/run/halo/app/content/permalinks/CategoryPermalinkPolicy.java
@@ -58,8 +58,8 @@ public class CategoryPermalinkPolicy
 
     @Override
     public void onPermalinkDelete(Category category) {
-        applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(category),
-            category.getStatusOrDefault().getPermalink()));
+        applicationContext.publishEvent(
+            new PermalinkIndexDeleteCommand(this, getLocator(category)));
     }
 
     private ExtensionLocator getLocator(Category category) {
diff --git a/src/main/java/run/halo/app/content/permalinks/ExtensionLocator.java b/src/main/java/run/halo/app/content/permalinks/ExtensionLocator.java
index 062363a8d..a9c256756 100644
--- a/src/main/java/run/halo/app/content/permalinks/ExtensionLocator.java
+++ b/src/main/java/run/halo/app/content/permalinks/ExtensionLocator.java
@@ -11,6 +11,20 @@ import run.halo.app.extension.GroupVersionKind;
  * @param slug extension slug
  */
 public record ExtensionLocator(GroupVersionKind gvk, String name, String slug) {
+
+    /**
+     * Create a new {@link ExtensionLocator} instance.
+     *
+     * @param gvk group version kind
+     * @param name extension name
+     * @param slug extension slug
+     */
+    public ExtensionLocator {
+        Objects.requireNonNull(gvk, "Group version kind must not be null");
+        Objects.requireNonNull(name, "Extension name must not be null");
+        Objects.requireNonNull(slug, "Extension slug must not be null");
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
diff --git a/src/main/java/run/halo/app/content/permalinks/PostPermalinkPolicy.java b/src/main/java/run/halo/app/content/permalinks/PostPermalinkPolicy.java
index 556cfed9f..15730bd02 100644
--- a/src/main/java/run/halo/app/content/permalinks/PostPermalinkPolicy.java
+++ b/src/main/java/run/halo/app/content/permalinks/PostPermalinkPolicy.java
@@ -10,6 +10,7 @@ import org.springframework.context.ApplicationContext;
 import org.springframework.stereotype.Component;
 import run.halo.app.core.extension.Post;
 import run.halo.app.extension.GroupVersionKind;
+import run.halo.app.infra.utils.PathUtils;
 import run.halo.app.theme.DefaultTemplateEnum;
 import run.halo.app.theme.router.PermalinkIndexAddCommand;
 import run.halo.app.theme.router.PermalinkIndexDeleteCommand;
@@ -23,7 +24,6 @@ import run.halo.app.theme.router.PermalinkWatch;
  */
 @Component
 public class PostPermalinkPolicy implements PermalinkPolicy<Post>, PermalinkWatch<Post> {
-
     private final GroupVersionKind gvk = GroupVersionKind.fromExtension(Post.class);
     private static final NumberFormat NUMBER_FORMAT = new DecimalFormat("00");
 
@@ -65,8 +65,7 @@ public class PostPermalinkPolicy implements PermalinkPolicy<Post>, PermalinkWatc
 
     @Override
     public void onPermalinkDelete(Post post) {
-        applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(post),
-            post.getStatusOrDefault().getPermalink()));
+        applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(post)));
     }
 
     private ExtensionLocator getLocator(Post post) {
@@ -85,6 +84,8 @@ public class PostPermalinkPolicy implements PermalinkPolicy<Post>, PermalinkWatc
         properties.put("year", String.valueOf(zonedDateTime.getYear()));
         properties.put("month", NUMBER_FORMAT.format(zonedDateTime.getMonthValue()));
         properties.put("day", NUMBER_FORMAT.format(zonedDateTime.getDayOfMonth()));
-        return PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(pattern, properties);
+
+        String simplifiedPattern = PathUtils.simplifyPathPattern(pattern);
+        return PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(simplifiedPattern, properties);
     }
 }
diff --git a/src/main/java/run/halo/app/content/permalinks/TagPermalinkPolicy.java b/src/main/java/run/halo/app/content/permalinks/TagPermalinkPolicy.java
index 979c28e52..9cf816550 100644
--- a/src/main/java/run/halo/app/content/permalinks/TagPermalinkPolicy.java
+++ b/src/main/java/run/halo/app/content/permalinks/TagPermalinkPolicy.java
@@ -57,8 +57,7 @@ public class TagPermalinkPolicy implements PermalinkPolicy<Tag>, PermalinkWatch<
 
     @Override
     public void onPermalinkDelete(Tag tag) {
-        applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(tag),
-            tag.getStatusOrDefault().getPermalink()));
+        applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, getLocator(tag)));
     }
 
     private ExtensionLocator getLocator(Tag tag) {
diff --git a/src/main/java/run/halo/app/core/extension/reconciler/SinglePageReconciler.java b/src/main/java/run/halo/app/core/extension/reconciler/SinglePageReconciler.java
index e1a23d376..9e8efc6de 100644
--- a/src/main/java/run/halo/app/core/extension/reconciler/SinglePageReconciler.java
+++ b/src/main/java/run/halo/app/core/extension/reconciler/SinglePageReconciler.java
@@ -133,8 +133,7 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
             .setPermalink(PathUtils.combinePath(singlePage.getSpec().getSlug()));
         ExtensionLocator locator = new ExtensionLocator(GVK, singlePage.getMetadata().getName(),
             singlePage.getSpec().getSlug());
-        applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, locator,
-            singlePage.getStatusOrDefault().getPermalink()));
+        applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, locator));
         templateRouteManager.changeTemplatePattern(DefaultTemplateEnum.SINGLE_PAGE.getValue());
     }
 
diff --git a/src/main/java/run/halo/app/infra/utils/PathUtils.java b/src/main/java/run/halo/app/infra/utils/PathUtils.java
index 0ee96f186..bd7b13819 100644
--- a/src/main/java/run/halo/app/infra/utils/PathUtils.java
+++ b/src/main/java/run/halo/app/infra/utils/PathUtils.java
@@ -47,4 +47,35 @@ public class PathUtils {
     public static String appendPathSeparatorIfMissing(String path) {
         return StringUtils.appendIfMissing(path, "/", "/");
     }
+
+    /**
+     * <p>Remove the regex in the path pattern placeholder.</p>
+     * <p>For example: </p>
+     * <ul>
+     * <li>'{@code /{year:\d{4}}/{month:\d{2}}}' &rarr; '{@code /{year}/{month}}'</li>
+     * <li>'{@code /archives/{year:\d{4}}/{month:\d{2}}}' &rarr; '{@code /archives/{year}/{month}
+     * }'</li>
+     * <li>'{@code /archives/{year:\d{4}}/{slug}}' &rarr; '{@code /archives/{year}/{slug}}'</li>
+     * </ul>
+     *
+     * @param pattern path pattern
+     * @return Simplified path pattern
+     */
+    public static String simplifyPathPattern(String pattern) {
+        if (StringUtils.isBlank(pattern)) {
+            return StringUtils.EMPTY;
+        }
+        String[] parts = StringUtils.split(pattern, '/');
+        for (int i = 0; i < parts.length; i++) {
+            String part = parts[i];
+            if (part.startsWith("{") && part.endsWith("}")) {
+                int colonIdx = part.indexOf(':');
+                if (colonIdx != -1) {
+                    parts[i] = part.substring(0, colonIdx) + part.charAt(part.length() - 1);
+                }
+
+            }
+        }
+        return combinePath(parts);
+    }
 }
diff --git a/src/main/java/run/halo/app/theme/router/PermalinkIndexDeleteCommand.java b/src/main/java/run/halo/app/theme/router/PermalinkIndexDeleteCommand.java
index 7c5990c53..d5bd880f3 100644
--- a/src/main/java/run/halo/app/theme/router/PermalinkIndexDeleteCommand.java
+++ b/src/main/java/run/halo/app/theme/router/PermalinkIndexDeleteCommand.java
@@ -12,19 +12,13 @@ import run.halo.app.content.permalinks.ExtensionLocator;
  */
 public class PermalinkIndexDeleteCommand extends ApplicationEvent {
     private final ExtensionLocator locator;
-    private final String permalink;
 
-    public PermalinkIndexDeleteCommand(Object source, ExtensionLocator locator, String permalink) {
+    public PermalinkIndexDeleteCommand(Object source, ExtensionLocator locator) {
         super(source);
         this.locator = locator;
-        this.permalink = permalink;
     }
 
     public ExtensionLocator getLocator() {
         return locator;
     }
-
-    public String getPermalink() {
-        return permalink;
-    }
 }
diff --git a/src/main/java/run/halo/app/theme/router/PermalinkIndexer.java b/src/main/java/run/halo/app/theme/router/PermalinkIndexer.java
index 1275f326b..075d8977b 100644
--- a/src/main/java/run/halo/app/theme/router/PermalinkIndexer.java
+++ b/src/main/java/run/halo/app/theme/router/PermalinkIndexer.java
@@ -3,12 +3,11 @@ package run.halo.app.theme.router;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import org.springframework.context.event.EventListener;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Component;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
 import run.halo.app.content.permalinks.ExtensionLocator;
 import run.halo.app.extension.GroupVersionKind;
 
@@ -21,9 +20,12 @@ import run.halo.app.extension.GroupVersionKind;
 @Component
 public class PermalinkIndexer {
     private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
-    private final MultiValueMap<GroupVersionKind, String> permalinkLookup =
-        new LinkedMultiValueMap<>();
-    private final Map<String, ExtensionLocator> permalinkLocatorMap = new HashMap<>();
+
+    private final Map<GvkName, String> gvkNamePermalinkLookup = new HashMap<>();
+    private final Map<String, ExtensionLocator> permalinkLocatorLookup = new HashMap<>();
+
+    record GvkName(GroupVersionKind gvk, String name) {
+    }
 
     /**
      * Register extension and permalink mapping.
@@ -34,8 +36,9 @@ public class PermalinkIndexer {
     public void register(ExtensionLocator locator, String permalink) {
         readWriteLock.writeLock().lock();
         try {
-            permalinkLookup.add(locator.gvk(), permalink);
-            permalinkLocatorMap.put(permalink, locator);
+            GvkName gvkName = new GvkName(locator.gvk(), locator.name());
+            gvkNamePermalinkLookup.put(gvkName, permalink);
+            permalinkLocatorLookup.put(permalink, locator);
         } finally {
             readWriteLock.writeLock().unlock();
         }
@@ -44,110 +47,87 @@ public class PermalinkIndexer {
     /**
      * Remove extension and permalink mapping.
      *
-     * @param locator extension locator
-     * @param permalink extension permalink for theme template route
+     * @param locator extension info
      */
-    public void remove(ExtensionLocator locator, String permalink) {
+    public void remove(ExtensionLocator locator) {
         readWriteLock.writeLock().lock();
         try {
-            List<String> permalinks = permalinkLookup.get(locator.gvk());
-            if (permalinks != null) {
-                permalinks.remove(permalink);
-                if (permalinks.isEmpty()) {
-                    permalinkLookup.remove(locator.gvk());
-                }
+            String permalink =
+                gvkNamePermalinkLookup.remove(new GvkName(locator.gvk(), locator.name()));
+            if (permalink != null) {
+                permalinkLocatorLookup.remove(permalink);
             }
-            permalinkLocatorMap.remove(permalink);
         } finally {
             readWriteLock.writeLock().unlock();
         }
     }
 
     /**
-     * Lookup extension locator by permalink.
+     * Gets permalink by {@link GroupVersionKind}.
+     *
+     * @param gvk group version kind
+     * @return permalinks
+     */
+    @NonNull
+    public List<String> getPermalinks(GroupVersionKind gvk) {
+        readWriteLock.readLock().lock();
+        try {
+            return gvkNamePermalinkLookup.entrySet()
+                .stream()
+                .filter(entry -> entry.getKey().gvk.equals(gvk))
+                .map(Map.Entry::getValue)
+                .toList();
+        } finally {
+            readWriteLock.readLock().unlock();
+        }
+    }
+
+    /**
+     * Lookup extension info by permalink.
      *
      * @param permalink extension permalink for theme template route
      * @return extension locator
      */
+    @Nullable
     public ExtensionLocator lookup(String permalink) {
         readWriteLock.readLock().lock();
         try {
-            return permalinkLocatorMap.get(permalink);
+            return permalinkLocatorLookup.get(permalink);
         } finally {
             readWriteLock.readLock().unlock();
         }
     }
 
     /**
-     * Gets permalinks by extension's {@link GroupVersionKind}.
+     * Lookup extension permalink by {@link GroupVersionKind} and {@code name}.
      *
-     * @param gvk extension's {@link GroupVersionKind}
-     * @return permalinks for extension's {@link GroupVersionKind}
+     * @param gvk group version kind
+     * @param name extension name
+     * @return {@code true} if contains, otherwise {@code false}
      */
-    public List<String> getPermalinks(GroupVersionKind gvk) {
+    public boolean containsName(GroupVersionKind gvk, String name) {
         readWriteLock.readLock().lock();
         try {
-            return Objects.requireNonNullElse(permalinkLookup.get(gvk), List.of());
+            return gvkNamePermalinkLookup.containsKey(new GvkName(gvk, name));
         } finally {
             readWriteLock.readLock().unlock();
         }
     }
 
     /**
-     * Lookup extension resource names by {@link GroupVersionKind}.
+     * Lookup extension permalink by {@link GroupVersionKind} and {@code slug}.
      *
-     * @param gvk extension's {@link GroupVersionKind}
-     * @return extension resource names
+     * @param gvk group version kind
+     * @param slug extension slug
+     * @return {@code true} if contains, otherwise {@code false}
      */
-    public List<String> getNames(GroupVersionKind gvk) {
+    public boolean containsSlug(GroupVersionKind gvk, String slug) {
         readWriteLock.readLock().lock();
         try {
-            return permalinkLocatorMap.values()
+            return permalinkLocatorLookup.values()
                 .stream()
-                .filter(locator -> locator.gvk().equals(gvk))
-                .map(ExtensionLocator::name)
-                .toList();
-        } finally {
-            readWriteLock.readLock().unlock();
-        }
-    }
-
-    /**
-     * Lookup extension resource slugs by {@link GroupVersionKind}.
-     *
-     * @param gvk extension's {@link GroupVersionKind}
-     * @return extension resource slugs
-     */
-    public List<String> getSlugs(GroupVersionKind gvk) {
-        readWriteLock.readLock().lock();
-        try {
-            return permalinkLocatorMap.values()
-                .stream()
-                .filter(locator -> locator.gvk().equals(gvk))
-                .map(ExtensionLocator::slug)
-                .toList();
-        } finally {
-            readWriteLock.readLock().unlock();
-        }
-    }
-
-    /**
-     * Lookup extension slug by resource name.
-     *
-     * @param gvk extension's {@link GroupVersionKind}
-     * @param name extension resource name
-     * @return extension slug specified by resource name
-     */
-    public String getSlugByName(GroupVersionKind gvk, String name) {
-        readWriteLock.readLock().lock();
-        try {
-            return permalinkLocatorMap.values()
-                .stream()
-                .filter(locator -> locator.gvk().equals(gvk))
-                .filter(locator -> locator.name().equals(name))
-                .findFirst()
-                .map(ExtensionLocator::slug)
-                .orElseThrow();
+                .anyMatch(locator -> locator.gvk().equals(gvk)
+                    && locator.slug().equals(slug));
         } finally {
             readWriteLock.readLock().unlock();
         }
@@ -163,10 +143,10 @@ public class PermalinkIndexer {
     public String getNameBySlug(GroupVersionKind gvk, String slug) {
         readWriteLock.readLock().lock();
         try {
-            return permalinkLocatorMap.values()
+            return permalinkLocatorLookup.values()
                 .stream()
-                .filter(locator -> locator.gvk().equals(gvk))
-                .filter(locator -> locator.slug().equals(slug))
+                .filter(locator -> locator.gvk().equals(gvk)
+                    && locator.slug().equals(slug))
                 .findFirst()
                 .map(ExtensionLocator::name)
                 .orElseThrow();
@@ -180,8 +160,8 @@ public class PermalinkIndexer {
      *
      * @return permalinkLookup map size
      */
-    protected long permalinkLookupSize() {
-        return permalinkLookup.size();
+    protected long gvkNamePermalinkMapSize() {
+        return gvkNamePermalinkLookup.size();
     }
 
     /**
@@ -190,7 +170,7 @@ public class PermalinkIndexer {
      * @return permalinkLocatorMap map size
      */
     protected long permalinkLocatorMapSize() {
-        return permalinkLocatorMap.size();
+        return permalinkLocatorLookup.size();
     }
 
     @EventListener(PermalinkIndexAddCommand.class)
@@ -200,12 +180,12 @@ public class PermalinkIndexer {
 
     @EventListener(PermalinkIndexDeleteCommand.class)
     public void onPermalinkDelete(PermalinkIndexDeleteCommand deleteCommand) {
-        remove(deleteCommand.getLocator(), deleteCommand.getPermalink());
+        remove(deleteCommand.getLocator());
     }
 
     @EventListener(PermalinkIndexUpdateCommand.class)
     public void onPermalinkUpdate(PermalinkIndexUpdateCommand updateCommand) {
-        remove(updateCommand.getLocator(), updateCommand.getPermalink());
+        remove(updateCommand.getLocator());
         register(updateCommand.getLocator(), updateCommand.getPermalink());
     }
 }
diff --git a/src/main/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategy.java
index e28f80372..bcb832cdb 100644
--- a/src/main/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategy.java
+++ b/src/main/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategy.java
@@ -40,9 +40,10 @@ public class ArchivesRouteStrategy implements TemplateRouterStrategy {
     public RouterFunction<ServerResponse> getRouteFunction(String template, String prefix) {
         return RouterFunctions
             .route(GET(prefix)
-                    .or(GET(PathUtils.combinePath(prefix, "/page/{page}")))
-                    .or(GET(PathUtils.combinePath(prefix, "/{year}/{month}")))
-                    .or(GET(PathUtils.combinePath(prefix, "/{year}/{month}/page/{page}")))
+                    .or(GET(PathUtils.combinePath(prefix, "/page/{page:\\d+}")))
+                    .or(GET(PathUtils.combinePath(prefix, "/{year:\\d{4}}/{month:\\d{2}}")))
+                    .or(GET(PathUtils.combinePath(prefix,
+                        "/{year:\\d{4}}/{month:\\d{2}}/page/{page:\\d+}")))
                     .and(accept(MediaType.TEXT_HTML)),
                 request -> ServerResponse.ok()
                     .render(DefaultTemplateEnum.ARCHIVES.getValue(),
diff --git a/src/main/java/run/halo/app/theme/router/strategy/CategoryRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/CategoryRouteStrategy.java
index 4b3848127..bae7d97ac 100644
--- a/src/main/java/run/halo/app/theme/router/strategy/CategoryRouteStrategy.java
+++ b/src/main/java/run/halo/app/theme/router/strategy/CategoryRouteStrategy.java
@@ -5,7 +5,6 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
 import static run.halo.app.theme.router.TemplateRouterStrategy.PageUrlUtils.pageNum;
 import static run.halo.app.theme.router.TemplateRouterStrategy.PageUrlUtils.totalPage;
 
-import java.util.List;
 import java.util.Map;
 import org.springframework.http.MediaType;
 import org.springframework.stereotype.Component;
@@ -53,13 +52,12 @@ public class CategoryRouteStrategy implements TemplateRouterStrategy {
     public RouterFunction<ServerResponse> getRouteFunction(String template, String prefix) {
         return RouterFunctions
             .route(GET(PathUtils.combinePath(prefix, "/{slug}"))
-                    .or(GET(PathUtils.combinePath(prefix, "/{slug}/page/{page}")))
+                    .or(GET(PathUtils.combinePath(prefix, "/{slug}/page/{page:\\d+}")))
                     .and(accept(MediaType.TEXT_HTML)),
                 request -> {
                     String slug = request.pathVariable("slug");
                     GroupVersionKind gvk = GroupVersionKind.fromExtension(Category.class);
-                    List<String> slugs = permalinkIndexer.getSlugs(gvk);
-                    if (!slugs.contains(slug)) {
+                    if (!permalinkIndexer.containsSlug(gvk, slug)) {
                         return ServerResponse.notFound().build();
                     }
                     String categoryName = permalinkIndexer.getNameBySlug(gvk, slug);
diff --git a/src/main/java/run/halo/app/theme/router/strategy/IndexRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/IndexRouteStrategy.java
index dbf5e7ebf..91a3ef2b5 100644
--- a/src/main/java/run/halo/app/theme/router/strategy/IndexRouteStrategy.java
+++ b/src/main/java/run/halo/app/theme/router/strategy/IndexRouteStrategy.java
@@ -39,7 +39,7 @@ public class IndexRouteStrategy implements TemplateRouterStrategy {
     @Override
     public RouterFunction<ServerResponse> getRouteFunction(String template, String pattern) {
         return RouterFunctions
-            .route(GET("/").or(GET("/page/{page}"))
+            .route(GET("/").or(GET("/page/{page:\\d+}"))
                     .and(accept(MediaType.TEXT_HTML)),
                 request -> ServerResponse.ok()
                     .render(DefaultTemplateEnum.INDEX.getValue(),
diff --git a/src/main/java/run/halo/app/theme/router/strategy/PostRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/PostRouteStrategy.java
index 45740b500..7d11ec7c4 100644
--- a/src/main/java/run/halo/app/theme/router/strategy/PostRouteStrategy.java
+++ b/src/main/java/run/halo/app/theme/router/strategy/PostRouteStrategy.java
@@ -4,7 +4,6 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
 import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
 
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -75,32 +74,27 @@ public class PostRouteStrategy implements TemplateRouterStrategy {
                     }
                     return ServerResponse.ok()
                         .render(DefaultTemplateEnum.POST.getValue(),
-                            Map.of(PostRequestParamPredicate.NAME_PARAM, name)
+                            Map.of(PostRequestParamPredicate.NAME_PARAM, name,
+                                "post", postByName(name))
                         );
                 });
         }
         return RouterFunctions
             .route(GET(pattern).and(accept(MediaType.TEXT_HTML)),
                 request -> {
-                    List<String> permalinks =
-                        permalinkIndexer.getPermalinks(
-                            PostRequestParamPredicate.GVK);
-                    boolean contains = permalinks.contains(request.path());
-                    if (!contains) {
+                    ExtensionLocator locator = permalinkIndexer.lookup(request.path());
+                    if (locator == null) {
                         return ServerResponse.notFound().build();
                     }
-                    ExtensionLocator extensionLocator =
-                        permalinkIndexer.lookup(request.path());
                     PathPattern parse = PathPatternParser.defaultInstance.parse(pattern);
                     PathPattern.PathMatchInfo pathMatchInfo =
                         parse.matchAndExtract(PathContainer.parsePath(request.path()));
                     Map<String, Object> model = new HashMap<>();
-                    model.put(PostRequestParamPredicate.NAME_PARAM,
-                        extensionLocator.name());
+                    model.put(PostRequestParamPredicate.NAME_PARAM, locator.name());
                     if (pathMatchInfo != null) {
                         model.putAll(pathMatchInfo.getUriVariables());
                     }
-                    model.put("post", postByName(extensionLocator.name()));
+                    model.put("post", postByName(locator.name()));
                     return ServerResponse.ok()
                         .render(DefaultTemplateEnum.POST.getValue(), model);
                 });
@@ -139,17 +133,13 @@ public class PostRouteStrategy implements TemplateRouterStrategy {
             }
 
             if (NAME_PARAM.equals(placeholderName)) {
-                return RequestPredicates.queryParam(paramName, name -> {
-                    List<String> names = permalinkIndexer.getNames(GVK);
-                    return names.contains(name);
-                });
+                return RequestPredicates.queryParam(paramName,
+                    name -> permalinkIndexer.containsName(GVK, name));
             }
 
             if (SLUG_PARAM.equals(placeholderName)) {
-                return RequestPredicates.queryParam(paramName, slug -> {
-                    List<String> slugs = permalinkIndexer.getSlugs(GVK);
-                    return slugs.contains(slug);
-                });
+                return RequestPredicates.queryParam(paramName,
+                    slug -> permalinkIndexer.containsSlug(GVK, slug));
             }
             throw new IllegalArgumentException(
                 String.format("Unknown param value placeholder [%s] in pattern [%s]",
diff --git a/src/main/java/run/halo/app/theme/router/strategy/SinglePageRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/SinglePageRouteStrategy.java
index 2f402e2f5..1b03b3f0c 100644
--- a/src/main/java/run/halo/app/theme/router/strategy/SinglePageRouteStrategy.java
+++ b/src/main/java/run/halo/app/theme/router/strategy/SinglePageRouteStrategy.java
@@ -4,7 +4,6 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
 
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.http.MediaType;
 import org.springframework.stereotype.Component;
@@ -48,8 +47,7 @@ public class SinglePageRouteStrategy implements TemplateRouterStrategy {
 
         RequestPredicate requestPredicate = request -> false;
 
-        List<String> permalinks =
-            Objects.requireNonNullElse(permalinkIndexer.getPermalinks(gvk), List.of());
+        List<String> permalinks = permalinkIndexer.getPermalinks(gvk);
         for (String permalink : permalinks) {
             requestPredicate = requestPredicate.or(RequestPredicates.GET(permalink));
         }
diff --git a/src/main/java/run/halo/app/theme/router/strategy/TagRouteStrategy.java b/src/main/java/run/halo/app/theme/router/strategy/TagRouteStrategy.java
index 837210ac2..e2965b569 100644
--- a/src/main/java/run/halo/app/theme/router/strategy/TagRouteStrategy.java
+++ b/src/main/java/run/halo/app/theme/router/strategy/TagRouteStrategy.java
@@ -5,7 +5,6 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
 import static run.halo.app.theme.router.TemplateRouterStrategy.PageUrlUtils.pageNum;
 import static run.halo.app.theme.router.TemplateRouterStrategy.PageUrlUtils.totalPage;
 
-import java.util.List;
 import java.util.Map;
 import org.springframework.http.MediaType;
 import org.springframework.stereotype.Component;
@@ -53,15 +52,15 @@ public class TagRouteStrategy implements TemplateRouterStrategy {
     public RouterFunction<ServerResponse> getRouteFunction(String template, String prefix) {
         return RouterFunctions
             .route(GET(PathUtils.combinePath(prefix, "/{slug}"))
-                    .or(GET(PathUtils.combinePath(prefix, "/{slug}/page/{page}")))
+                    .or(GET(PathUtils.combinePath(prefix, "/{slug}/page/{page:\\d+}")))
                     .and(accept(MediaType.TEXT_HTML)),
                 request -> {
                     GroupVersionKind gvk = GroupVersionKind.fromExtension(Tag.class);
-                    List<String> slugs = permalinkIndexer.getSlugs(gvk);
                     String slug = request.pathVariable("slug");
-                    if (!slugs.contains(slug)) {
+                    if (!permalinkIndexer.containsSlug(gvk, slug)) {
                         return ServerResponse.notFound().build();
                     }
+
                     String name = permalinkIndexer.getNameBySlug(gvk, slug);
                     return ServerResponse.ok()
                         .render(DefaultTemplateEnum.TAG.getValue(),
diff --git a/src/main/resources/extensions/system-setting.yaml b/src/main/resources/extensions/system-setting.yaml
index c4295b6c8..e8e073b73 100644
--- a/src/main/resources/extensions/system-setting.yaml
+++ b/src/main/resources/extensions/system-setting.yaml
@@ -32,7 +32,7 @@ spec:
         label: "默认角色"
         name: defaultRole
         validation: required
-  - group: themeRules
+  - group: routeRules
     label: 主题模板路由设置
     formSchema:
       - $formkit: text
@@ -58,9 +58,9 @@ spec:
           - /archives/{name}
           - /?p={name}
           - /?p={slug}
-          - /{year}/{slug}
-          - /{year}/{month}/{slug}
-          - /{year}/{month}/{day}/{slug}
+          - /{year:\d{4}}/{slug}
+          - /{year:\d{4}}/{month:\d{2}}/{slug}
+          - /{year:\d{4}}/{month:\d{2}}/{day:\d{2}}/{slug}
         name: post
         validation: required
   - group: post
diff --git a/src/test/java/run/halo/app/infra/utils/PathUtilsTest.java b/src/test/java/run/halo/app/infra/utils/PathUtilsTest.java
index 104f200df..fc12d43ea 100644
--- a/src/test/java/run/halo/app/infra/utils/PathUtilsTest.java
+++ b/src/test/java/run/halo/app/infra/utils/PathUtilsTest.java
@@ -43,4 +43,17 @@ class PathUtilsTest {
         s = PathUtils.appendPathSeparatorIfMissing(null);
         assertThat(s).isEqualTo(null);
     }
+
+    @Test
+    void simplifyPathPattern() {
+        assertThat(PathUtils.simplifyPathPattern("/a/b/c")).isEqualTo("/a/b/c");
+        assertThat(PathUtils.simplifyPathPattern("/a/{b}/c")).isEqualTo("/a/{b}/c");
+        assertThat(PathUtils.simplifyPathPattern("/a/{b}/*")).isEqualTo("/a/{b}/*");
+        assertThat(PathUtils.simplifyPathPattern("/archives/{year:\\d{4}}/{month:\\d{2}}"))
+            .isEqualTo("/archives/{year}/{month}");
+        assertThat(PathUtils.simplifyPathPattern("/archives/{year:\\d{4}}/{slug}"))
+            .isEqualTo("/archives/{year}/{slug}");
+        assertThat(PathUtils.simplifyPathPattern("/archives/{year:\\d{4}}/page/{page:\\d+}"))
+            .isEqualTo("/archives/{year}/page/{page}");
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/run/halo/app/theme/router/PermalinkIndexerTest.java b/src/test/java/run/halo/app/theme/router/PermalinkIndexerTest.java
index 2bb396d9c..29a016b1d 100644
--- a/src/test/java/run/halo/app/theme/router/PermalinkIndexerTest.java
+++ b/src/test/java/run/halo/app/theme/router/PermalinkIndexerTest.java
@@ -49,7 +49,7 @@ class PermalinkIndexerTest {
         assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(1);
         assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(1);
 
-        permalinkIndexer.remove(locator, "/fake-permalink");
+        permalinkIndexer.remove(locator);
         assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(0);
         assertThat(permalinkIndexer.permalinkLocatorMapSize()).isEqualTo(0);
     }
@@ -77,8 +77,8 @@ class PermalinkIndexerTest {
         ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug");
         permalinkIndexer.register(locator, "/test-permalink");
 
-        List<String> names = permalinkIndexer.getNames(gvk);
-        assertThat(names).isEqualTo(List.of("fake-name", "test-name"));
+        assertThat(permalinkIndexer.containsName(gvk, "test-name")).isTrue();
+        assertThat(permalinkIndexer.containsName(gvk, "nothing")).isFalse();
     }
 
     @Test
@@ -86,24 +86,9 @@ class PermalinkIndexerTest {
         ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug");
         permalinkIndexer.register(locator, "/test-permalink");
 
-        List<String> slugs = permalinkIndexer.getSlugs(gvk);
-        assertThat(slugs).isEqualTo(List.of("fake-slug", "test-slug"));
-    }
-
-    @Test
-    void getSlugByName() {
-        ExtensionLocator locator = new ExtensionLocator(gvk, "test-name", "test-slug");
-        permalinkIndexer.register(locator, "/test-permalink");
-
-        String slugByName = permalinkIndexer.getSlugByName(gvk, "test-name");
-        assertThat(slugByName).isEqualTo("test-slug");
-
-        slugByName = permalinkIndexer.getSlugByName(gvk, "fake-name");
-        assertThat(slugByName).isEqualTo("fake-slug");
-
-        assertThatThrownBy(() -> {
-            permalinkIndexer.getSlugByName(gvk, "nothing");
-        }).isInstanceOf(NoSuchElementException.class);
+        assertThat(permalinkIndexer.containsSlug(gvk, "fake-slug")).isTrue();
+        assertThat(permalinkIndexer.containsSlug(gvk, "test-slug")).isTrue();
+        assertThat(permalinkIndexer.containsSlug(gvk, "nothing")).isFalse();
     }
 
     @Test
diff --git a/src/test/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategyTest.java b/src/test/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategyTest.java
index bb20fa1ba..5ef45f045 100644
--- a/src/test/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategyTest.java
+++ b/src/test/java/run/halo/app/theme/router/strategy/ArchivesRouteStrategyTest.java
@@ -82,14 +82,20 @@ class ArchivesRouteStrategyTest {
             .expectStatus().isOk();
 
         client.get()
-            .uri(prefix + "/year/month")
+            .uri(prefix + "/2022/09")
             .exchange()
             .expectStatus().isOk();
 
         client.get()
-            .uri(prefix + "/year/month/page/1")
+            .uri(prefix + "/2022/08/page/1")
             .exchange()
             .expectStatus().isOk();
+
+        client.get()
+            .uri(prefix + "/2022/8/page/1")
+            .exchange()
+            .expectStatus()
+            .isEqualTo(HttpStatus.NOT_FOUND);
     }
 
     @Test
diff --git a/src/test/java/run/halo/app/theme/router/strategy/CategoryRouteStrategyTest.java b/src/test/java/run/halo/app/theme/router/strategy/CategoryRouteStrategyTest.java
index af30cc862..fa6a68e33 100644
--- a/src/test/java/run/halo/app/theme/router/strategy/CategoryRouteStrategyTest.java
+++ b/src/test/java/run/halo/app/theme/router/strategy/CategoryRouteStrategyTest.java
@@ -20,6 +20,8 @@ import org.springframework.web.reactive.function.server.RouterFunction;
 import org.springframework.web.reactive.function.server.ServerResponse;
 import org.springframework.web.reactive.result.view.ViewResolver;
 import reactor.core.publisher.Mono;
+import run.halo.app.core.extension.Category;
+import run.halo.app.extension.GroupVersionKind;
 import run.halo.app.extension.ListResult;
 import run.halo.app.theme.DefaultTemplateEnum;
 import run.halo.app.theme.finders.PostFinder;
@@ -47,8 +49,11 @@ class CategoryRouteStrategyTest {
 
     @BeforeEach
     void setUp() {
-        when(permalinkIndexer.getSlugs(any()))
-            .thenReturn(List.of("category-slug-1", "category-slug-2"));
+        GroupVersionKind gvk = GroupVersionKind.fromExtension(Category.class);
+        when(permalinkIndexer.containsSlug(eq(gvk), eq("category-slug-1")))
+            .thenReturn(true);
+        when(permalinkIndexer.containsSlug(eq(gvk), eq("category-slug-2")))
+            .thenReturn(true);
         when(permalinkIndexer.getNameBySlug(any(), eq("category-slug-1")))
             .thenReturn("category-name-1");
         when(permalinkIndexer.getNameBySlug(any(), eq("category-slug-2")))
diff --git a/src/test/java/run/halo/app/theme/router/strategy/PostRouteStrategyTest.java b/src/test/java/run/halo/app/theme/router/strategy/PostRouteStrategyTest.java
index 161bb0ca9..8359e3cd9 100644
--- a/src/test/java/run/halo/app/theme/router/strategy/PostRouteStrategyTest.java
+++ b/src/test/java/run/halo/app/theme/router/strategy/PostRouteStrategyTest.java
@@ -5,7 +5,6 @@ import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.when;
 
-import java.util.List;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -64,9 +63,6 @@ class PostRouteStrategyTest {
 
         piling();
 
-        when(permalinkIndexer.getPermalinks(any()))
-            .thenReturn(List.of("/posts-test/fake-slug"));
-
         client.get()
             .uri("/posts-test/fake-slug")
             .exchange()
@@ -84,9 +80,6 @@ class PostRouteStrategyTest {
 
         piling();
 
-        when(permalinkIndexer.getPermalinks(any()))
-            .thenReturn(List.of("/posts-test/fake-name"));
-
         client.get()
             .uri("/posts-test/fake-name")
             .exchange()
@@ -104,9 +97,6 @@ class PostRouteStrategyTest {
 
         piling();
 
-        when(permalinkIndexer.getPermalinks(any()))
-            .thenReturn(List.of("/2022/08/fake-slug"));
-
         client.get()
             .uri("/2022/08/fake-slug")
             .exchange()
@@ -144,14 +134,11 @@ class PostRouteStrategyTest {
     }
 
     private void piling() {
-        lenient().when(permalinkIndexer.getNames(any()))
-            .thenReturn(List.of("fake-name"));
-
-        lenient().when(permalinkIndexer.getSlugs(any()))
-            .thenReturn(List.of("fake-slug"));
-
-        lenient().when(permalinkIndexer.getSlugs(any()))
-            .thenReturn(List.of("fake-slug"));
+        GroupVersionKind postGvk = GroupVersionKind.fromExtension(Post.class);
+        lenient().when(permalinkIndexer.containsName(eq(postGvk), eq("fake-name")))
+            .thenReturn(true);
+        lenient().when(permalinkIndexer.containsSlug(eq(postGvk), eq("fake-slug")))
+            .thenReturn(true);
 
         lenient().when(permalinkIndexer.getNameBySlug(any(), eq("fake-slug")))
             .thenReturn("fake-name");
diff --git a/src/test/java/run/halo/app/theme/router/strategy/TagRouteStrategyTest.java b/src/test/java/run/halo/app/theme/router/strategy/TagRouteStrategyTest.java
index 6a8bf6b72..3b2c50e78 100644
--- a/src/test/java/run/halo/app/theme/router/strategy/TagRouteStrategyTest.java
+++ b/src/test/java/run/halo/app/theme/router/strategy/TagRouteStrategyTest.java
@@ -20,6 +20,8 @@ import org.springframework.web.reactive.function.server.RouterFunction;
 import org.springframework.web.reactive.function.server.ServerResponse;
 import org.springframework.web.reactive.result.view.ViewResolver;
 import reactor.core.publisher.Mono;
+import run.halo.app.core.extension.Tag;
+import run.halo.app.extension.GroupVersionKind;
 import run.halo.app.extension.ListResult;
 import run.halo.app.theme.DefaultTemplateEnum;
 import run.halo.app.theme.finders.PostFinder;
@@ -48,8 +50,9 @@ class TagRouteStrategyTest {
     void setUp() {
         lenient().when(postFinder.listByTag(anyInt(), anyInt(), any()))
             .thenReturn(new ListResult<>(1, 10, 0, List.of()));
-        when(permalinkIndexer.getSlugs(any()))
-            .thenReturn(List.of("fake-slug"));
+        GroupVersionKind gvk = GroupVersionKind.fromExtension(Tag.class);
+        when(permalinkIndexer.containsSlug(eq(gvk), eq("fake-slug")))
+            .thenReturn(true);
         when(permalinkIndexer.getNameBySlug(any(), eq("fake-slug")))
             .thenReturn("fake-name");
     }