This is an automated email from the ASF dual-hosted git repository.

davidarthur pushed a commit to branch markdown
in repository https://gitbox.apache.org/repos/asf/kafka-site.git


The following commit(s) were added to refs/heads/markdown by this push:
     new c6d1a8143 KAFKA-20006 Fix broken links and updates to README (#761)
c6d1a8143 is described below

commit c6d1a8143b84f7db71351d271496918b729c34fc
Author: Harish Vishwanath <[email protected]>
AuthorDate: Thu Dec 18 14:20:29 2025 -0800

    KAFKA-20006 Fix broken links and updates to README (#761)
    
    * KAFKA-20006: Implement permalinks and redirects to continue to support 
older links.
    
    * KAFKA-20008: Update README with instructions on adding a new blog post
    
    * fix link for /design
    
    
    Reviewers: David Arthur <[email protected]>
---
 README.md                                | 102 +++++++++++++++++++--------
 content/en/_index.md                     |   4 +-
 content/en/community/_index.md           |   3 +
 content/en/community/books_and_papers.md |   5 +-
 content/en/community/committers.md       |   3 +
 content/en/community/contact.md          |   3 +
 content/en/community/cve-list.md         |   3 +
 content/en/community/developer.md        |   7 ++
 content/en/community/downloads.md        |   5 +-
 content/en/community/events.md           |   3 +
 content/en/community/podcasts.md         |   3 +
 content/en/community/project_security.md |   3 +
 content/en/community/videos.md           |   3 +
 content/en/design.md                     |   9 +++
 content/en/documentation/_index.md       |   7 ++
 content/en/documentation/streams.md      |   9 +++
 content/en/intro.md                      |  11 +++
 content/en/protocol.md                   |   9 +++
 content/en/quickstart.md                 |  11 +++
 content/en/testimonials/_index.md        |   4 ++
 content/en/uses.md                       |   9 +++
 hugo.yaml                                | 101 +++++++++++++-------------
 layouts/partials/hooks/body-end.html     |   6 +-
 layouts/shortcodes/doc-redirect.html     | 117 +++++++++++++++++++++++++++++++
 layouts/shortcodes/include-latest.html   |   8 +++
 layouts/shortcodes/version-redirect.html |  11 +++
 layouts/simple/single.html               |  31 ++++++++
 scripts/verify_redirects.py              | 103 +++++++++++++++++++++++++++
 28 files changed, 508 insertions(+), 85 deletions(-)

diff --git a/README.md b/README.md
index bd704ef6c..61831be46 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,7 @@ Each version directory contains the complete documentation 
for that specific Kaf
 
 ## Updating the Documentation Website
 
-### Adding a New Version
+### Adding documentation for a new release
 
 When releasing a new documentation version (e.g., version 4.2 / "42"), follow 
these steps:
 
@@ -58,35 +58,39 @@ When releasing a new documentation version (e.g., version 
4.2 / "42"), follow th
 Create a new directory in `content/en/` for the new version (e.g., `42/` for 
version 4.2)
 
 #### 2. Update Version Parameters in `hugo.yaml`
-
-**Update the centralized version parameters (Lines 142-144):**
-```yaml
-params:
-  # Latest documentation version - UPDATE THIS WHEN RELEASING NEW VERSION
-  latest_version: "42"           # Change from "41" to "42"
-  latest_version_number: "4.2"   # Change from "4.1" to "4.2"
-```
-
-**Update the version parameter (Line ~180):**
-```yaml
-  version: 4.2  # Change from 4.1 to 4.2
-```
-
-**Update the latest version URL (Line ~283):**
-```yaml
-  url_latest_version: /42/  # Change from /41/ to /42/
-```
-
-**Add new version to versions list and mark previous as archived (Line ~184):**
-```yaml
-  versions:
-    - version: "4.2"
-      url: /42/
-    - version: "4.1"
-      url: /41/
-      archived_version: true  # Mark previous version as archived
-    # ... other versions ...
-```
+ 
+ Locate the **Version Configuration** block at the top of the `params` section 
(around line 245). Update the following fields:
+ 
+ 1.  `latest_version`: Set to the new version string (e.g., "42").
+ 2.  `latest_version_number`: Set to the new version number (e.g., "4.2").
+ 3.  `version`: Update to the new version (e.g., 4.2).
+ 4.  `url_latest_version`: Update the link (e.g., `/42/`).
+ 5.  `versions`:
+     -   Add the new version to the top of the list.
+     -   Mark the previous version as `archived_version: true`.
+ 
+ ```yaml
+   # Latest documentation version - UPDATE THIS WHEN RELEASING NEW VERSION
+   latest_version: "42"
+   latest_version_number: "4.2"
+   
+   # ...
+   
+   version: 4.2
+ 
+   # ...
+ 
+   url_latest_version: /42/
+ 
+   # ...
+ 
+   versions:
+     - version: "4.2"
+       url: /42/
+     - version: "4.1"
+       url: /41/
+       archived_version: true
+ ```
 
 #### What Updates Automatically
 
@@ -145,6 +149,44 @@ The website uses Hugo's data templates to automatically 
generate the testimonial
 2. For common content (e.g., landing page, community docs):
    - Edit files directly in `content/en/`
 
+### Front Matter Guide
+
+Detailed information about the Front Matter fields used in this site:
+
+- `title`: The title of the page or post.
+- `linkTitle`: (Optional) Short title used in sidebars and menus.
+- `date`: Publication date (YYYY-MM-DD).
+- `author`: Author name (often with GitHub handle). Typically used for blogs.
+- `weight`: (Optional) Controls ordering in lists/menus (lower numbers appear 
first).
+- `description`: (Optional) Brief summary for SEO and previews.
+- `type`: Used to specify the layout type (e.g., `type: docs` for 
documentation pages).
+- `aliases`: (Optional) List of old URLs that should redirect to this page.
+- `url`: (Optional) Overrides the default URL path constructed from the 
filename.
+
+For more details, see the [Hugo Front Matter 
Documentation](https://gohugo.io/content-management/front-matter/).
+
+### Adding a New Blog Post
+
+Blog posts are located in `content/en/blog/`. The most common use case is 
adding a release announcement.
+
+#### Adding a Release Blog Post
+
+1. Create a new markdown file in `content/en/blog/releases/` using kebab-case 
for the filename (e.g., `ak-4.3.0.md`).
+2. Add the required Front Matter. See the [Front Matter 
Guide](#front-matter-guide) for details on fields.
+
+**Example Front Matter for a Release Post:**
+
+```yaml
+---
+date: 2025-01-01
+title: "Apache Kafka 4.3.0 Release Announcement"
+linkTitle: "AK 4.3.0"
+author: "Author Name (@github_handle)"
+---
+```
+
+3. Write your content below the Front Matter.
+
 ## Build and Test
 
 ### Prerequisites
diff --git a/content/en/_index.md b/content/en/_index.md
index 4990262b8..637cdd091 100644
--- a/content/en/_index.md
+++ b/content/en/_index.md
@@ -7,7 +7,7 @@ title: Apache Kafka
 <a class="btn btn-lg btn-primary me-3 mb-4" href="{{< param 
"url_latest_version" >}}">
   Learn More <i class="fas fa-arrow-alt-circle-right ms-2"></i>
 </a>
-<a class="btn btn-lg btn-secondary me-3 mb-4" href="/community/downloads/">
+<a class="btn btn-lg btn-secondary me-3 mb-4" href="/downloads/">
   Download <i class="fa-solid fa-download ms-2 "></i>
 </a>
 <p class="lead mt-5"><i>More than 80% of all Fortune 100 companies trust, and 
use Apache Kafka.</i></p>
@@ -56,7 +56,7 @@ Apache Kafka is an open-source distributed event streaming 
platform used by thou
   Above is a snapshot of the number of top-ten largest companies using Kafka, 
per-industry.
   <br/> 
   <br/>
-  <a href="/testimonials/" class="btn btn-lg btn-primary">See full list <i 
class="fas fa-arrow-alt-circle-right ms-2"></i></a>
+  <a href="/powered-by/" class="btn btn-lg btn-primary">See full list <i 
class="fas fa-arrow-alt-circle-right ms-2"></i></a>
 </p>
 
 {{< /blocks/section >}}
diff --git a/content/en/community/_index.md b/content/en/community/_index.md
index d0361a87b..d4e7d0cd8 100644
--- a/content/en/community/_index.md
+++ b/content/en/community/_index.md
@@ -1,6 +1,9 @@
 ---
 title: Community
 type: docs
+aliases:
+  - "/project"
+  - "/project.html"
 # Add blocks of content here to add more sections to the community page
 ---
 
diff --git a/content/en/community/books_and_papers.md 
b/content/en/community/books_and_papers.md
index 549f541f6..93a13936a 100644
--- a/content/en/community/books_and_papers.md
+++ b/content/en/community/books_and_papers.md
@@ -1,5 +1,8 @@
 ---
-title: "Books And Papers"
+title: Books and Papers
+aliases:
+  - "/books-and-papers"
+  - "/books-and-papers.html"
 type: docs
 ---
 
diff --git a/content/en/community/committers.md 
b/content/en/community/committers.md
index 1e98dbdbc..fbf05f045 100644
--- a/content/en/community/committers.md
+++ b/content/en/community/committers.md
@@ -1,6 +1,9 @@
 ---
 title: "The Committers"
 type: docs
+aliases:
+  - "/committers"
+  - "/committers.html"
 ---
 
 {{< about/committers >}}
diff --git a/content/en/community/contact.md b/content/en/community/contact.md
index abbefc17b..c41cc21a4 100644
--- a/content/en/community/contact.md
+++ b/content/en/community/contact.md
@@ -1,6 +1,9 @@
 ---
 title: Contact
 type: docs
+aliases:
+  - "/contact"
+  - "/contact.html"
 ---
 
 ## Mailing Lists
diff --git a/content/en/community/cve-list.md b/content/en/community/cve-list.md
index e69b0d5d7..01787da63 100644
--- a/content/en/community/cve-list.md
+++ b/content/en/community/cve-list.md
@@ -1,6 +1,9 @@
 ---
 title: CVE List
 type: docs
+aliases:
+    - "/cve-list"
+    - "/cve-list.html"
 ---
 
 ## Apache Kafka Security Vulnerabilities
diff --git a/content/en/community/developer.md 
b/content/en/community/developer.md
index 6bfaddfb9..6f837c033 100644
--- a/content/en/community/developer.md
+++ b/content/en/community/developer.md
@@ -1,6 +1,13 @@
 ---
 title: Developer Guide
 type: docs
+aliases:
+    - "/contributing"
+    - "/contributing.html"
+    - "/coding-guide"
+    - "/coding-guide.html"
+    - "/code"
+    - "/code.html"
 ---
 
 ## Getting the code
diff --git a/content/en/community/downloads.md 
b/content/en/community/downloads.md
index 3a88fddd7..5bf4199c7 100644
--- a/content/en/community/downloads.md
+++ b/content/en/community/downloads.md
@@ -1,10 +1,11 @@
 ---
 title: "Downloads"
 type: docs
+aliases:
+  - "/downloads"
+  - "/downloads.html"
 ---
 
-
-
 The project goal is to have 3 releases a year, which means a release every 4 
months. Bugfix releases are made as needed for supported releases only. It is 
possible to verify every download by following these 
[procedures](https://www.apache.org/info/verification.html) and using these 
[KEYS](https://downloads.apache.org/kafka/KEYS). 
 
 ## Supported releases
diff --git a/content/en/community/events.md b/content/en/community/events.md
index ab9904e42..3907f1442 100644
--- a/content/en/community/events.md
+++ b/content/en/community/events.md
@@ -1,6 +1,9 @@
 ---
 title: "Events"
 type: docs
+aliases:
+  - "/events"
+  - "/events.html"
 ---
 
 
diff --git a/content/en/community/podcasts.md b/content/en/community/podcasts.md
index aed025b14..6df026615 100644
--- a/content/en/community/podcasts.md
+++ b/content/en/community/podcasts.md
@@ -1,6 +1,9 @@
 ---
 title: "Podcasts"
 type: docs
+aliases:
+  - "/podcasts"
+  - "/podcasts.html"
 ---
 
 
diff --git a/content/en/community/project_security.md 
b/content/en/community/project_security.md
index c5460e82a..28024fc69 100644
--- a/content/en/community/project_security.md
+++ b/content/en/community/project_security.md
@@ -1,6 +1,9 @@
 ---
 title: "Project Security"
 type: docs
+aliases:
+    - "/project-security"
+    - "/project-security.html"
 ---
 
 # Kafka security
diff --git a/content/en/community/videos.md b/content/en/community/videos.md
index e529d096e..b4845fefe 100644
--- a/content/en/community/videos.md
+++ b/content/en/community/videos.md
@@ -1,6 +1,9 @@
 ---
 title: "Videos"
 type: docs
+aliases:
+  - "/videos"
+  - "/videos.html"
 ---
 
 # Best Kafka Summit Videos
diff --git a/content/en/design.md b/content/en/design.md
new file mode 100644
index 000000000..4a8c25bda
--- /dev/null
+++ b/content/en/design.md
@@ -0,0 +1,9 @@
+---
+title: "Design"
+url: "/design"
+aliases:
+    - "/design.html"
+type: simple
+---
+
+{{< version-redirect path="design/design" >}}
diff --git a/content/en/documentation/_index.md 
b/content/en/documentation/_index.md
new file mode 100644
index 000000000..680e9c095
--- /dev/null
+++ b/content/en/documentation/_index.md
@@ -0,0 +1,7 @@
+---
+title: "Documentation Redirect"
+url: "/documentation"
+robots: "noindex"
+---
+
+{{< doc-redirect >}}
diff --git a/content/en/documentation/streams.md 
b/content/en/documentation/streams.md
new file mode 100644
index 000000000..88cc0d64c
--- /dev/null
+++ b/content/en/documentation/streams.md
@@ -0,0 +1,9 @@
+---
+title: "Kafka Streams"
+url: "/documentation/streams"
+aliases:
+    - "/documentation/streams.html"
+type: simple
+---
+
+{{< version-redirect path="streams/introduction" >}}
diff --git a/content/en/intro.md b/content/en/intro.md
new file mode 100644
index 000000000..3d3dda5be
--- /dev/null
+++ b/content/en/intro.md
@@ -0,0 +1,11 @@
+---
+title: "Introduction"
+url: "/intro"
+aliases:
+    - "/intro.html"
+type: simple
+---
+
+{{< youtube vHbvbwSEYGo >}}
+
+{{< include-latest path="getting-started/introduction" >}}
diff --git a/content/en/protocol.md b/content/en/protocol.md
new file mode 100644
index 000000000..3a6c8758a
--- /dev/null
+++ b/content/en/protocol.md
@@ -0,0 +1,9 @@
+---
+title: "Protocol"
+url: "/protocol"
+aliases:
+    - "/protocol.html"
+type: simple
+---
+
+{{< version-redirect path="design/protocol" >}}
diff --git a/content/en/quickstart.md b/content/en/quickstart.md
new file mode 100644
index 000000000..3c3c0a28c
--- /dev/null
+++ b/content/en/quickstart.md
@@ -0,0 +1,11 @@
+---
+title: "Quickstart"
+url: "/quickstart"
+aliases:
+    - "/quickstart.html"
+type: simple
+---
+
+{{< youtube vHbvbwSEYGo >}}
+
+{{< include-latest path="getting-started/quickstart" >}}
diff --git a/content/en/testimonials/_index.md 
b/content/en/testimonials/_index.md
index a29e32d05..8931df789 100644
--- a/content/en/testimonials/_index.md
+++ b/content/en/testimonials/_index.md
@@ -1,6 +1,10 @@
 ---
 title: Powered By
+url: "/powered-by"
 body_class: testimonials-page
+aliases:
+  - "/powered-by.html"
+  - "/testimonials"
 ---
 
 {{% blocks/cover title="Powered By" image_anchor="bottom" height="auto" 
align="left" %}}
diff --git a/content/en/uses.md b/content/en/uses.md
new file mode 100644
index 000000000..544c86582
--- /dev/null
+++ b/content/en/uses.md
@@ -0,0 +1,9 @@
+---
+title: "Uses"
+url: "/uses"
+aliases:
+    - "/uses.html"
+type: simple
+---
+
+{{< include-latest path="getting-started/uses" >}}
diff --git a/hugo.yaml b/hugo.yaml
index 12b907c75..e52cf9a9e 100644
--- a/hugo.yaml
+++ b/hugo.yaml
@@ -98,73 +98,78 @@ menu:
       weight: 10
     - name: "Introduction"
       parent: "Get Started"
-      url: "{{VERSION}}/getting-started/introduction/"
+      url: "/intro"
       weight: 11
     - name: "Quickstart"
       parent: "Get Started"
-      url: "{{VERSION}}/getting-started/quickstart/"
+      url: "/quickstart"
       weight: 12
     - name: "Use Cases"
       parent: "Get Started"
-      url: "{{VERSION}}/getting-started/uses/"
+      url: "/uses"
       weight: 13
     - name: "Books and Papers"
       parent: "Get Started"
-      url: "/community/books_and_papers/"
+      url: "/books-and-papers"
       weight: 14
     - name: "Videos"
       parent: "Get Started"
-      url: "/community/videos/"
+      url: "/videos"
       weight: 15
     - name: "Podcasts"
       parent: "Get Started"
-      url: "/community/podcasts/"
+      url: "/podcasts"
       weight: 16
 
     # Docs menu
     - name: "Docs"
-      url: "{{VERSION}}"
+      url: "/documentation"
       weight: 20
     - name: "Key Concepts"
       parent: "Docs"
-      url: "{{VERSION}}/getting-started/"
+      url: "/documentation#gettingStarted"
       weight: 21
     - name: "APIs"
       parent: "Docs"
-      url: "{{VERSION}}/apis/"
+      url: "/documentation#api"
       weight: 22
     - name: "Configuration"
       parent: "Docs"
-      url: "{{VERSION}}/configuration/"
+      url: "/documentation#configuration"
       weight: 23
     - name: "Design"
       parent: "Docs"
-      url: "{{VERSION}}/design/"
+      url: "/documentation#design"
       weight: 24
     - name: "Implementation"
       parent: "Docs"
-      url: "{{VERSION}}/implementation/"
+      url: "/documentation#implementation"
       weight: 25
     - name: "Operations"
       parent: "Docs"
-      url: "{{VERSION}}/operations/"
+      url: "/documentation#operations"
       weight: 26
     - name: "Security"
+      identifier: "docs_security"
       parent: "Docs"
-      url: "{{VERSION}}/security/"
+      url: "/documentation#security"
       weight: 27
-    - name: "Kafka Connect"
+    - name: "Clients"
       parent: "Docs"
-      url: "{{VERSION}}/kafka-connect/"
+      url: "https://cwiki.apache.org/confluence/display/KAFKA/Clients";
       weight: 28
-    - name: "Kafka Streams"
+    - name: "Kafka Connect"
       parent: "Docs"
-      url: "{{VERSION}}/streams/"
+      url: "/documentation#connect"
       weight: 29
+    - name: "Kafka Streams"
+      parent: "Docs"
+      url: "/documentation/streams"
+      weight: 30
 
     # Powered By menu item
     - name: "Powered By"
-      url: "/testimonials/"
+      url: "/powered-by/"
       weight: 35
 
     # Community menu
@@ -181,7 +186,7 @@ menu:
       weight: 42
     - name: "Project Info"
       parent: "Community"
-      url: "/community/"
+      url: "/project/"
       weight: 43
     - name: "Trademark"
       parent: "Community"
@@ -221,6 +226,7 @@ menu:
       url: "https://www.apache.org/foundation/thanks.html";
       weight: 54
     - name: "Security"
+      identifier: "apache_security"
       parent: "Apache"
       url: "https://www.apache.org/security/";
       weight: 55
@@ -240,25 +246,16 @@ params:
   latest_version: "41"
   latest_version_number: "4.1"
   
-  # Favicon configuration
-  favicon: /images/apache.png
-  
-  print: 
-    disable_toc: false
-  taxonomy:
-    # set taxonomyCloud = [] to hide taxonomy clouds
-    taxonomyCloud: [tags, categories]
-
-    # If used, must have same length as taxonomyCloud
-    taxonomyCloudTitle: [Tag Cloud, Categories]
-
-    # set taxonomyPageHeader = [] to hide taxonomies on the page headers
-    taxonomyPageHeader: [tags, categories]
-
-  privacy_policy: 
https://privacy.apache.org/policies/privacy-policy-public.html
+  # The version number for the version of the docs represented in this doc set.
+  # Used in the "version-banner" partial to display a version number for the
+  # current doc set.
+  # NOTE: This is automatically set from latest_version_number parameter above
+  version: 4.1
 
-  # First one is picked as the Twitter card image if not set on page.
-  # images: [images/project-illustration.png]
+  # A link to latest version of the docs. Used in the "version-banner" partial 
to
+  # point people to the main doc site.
+  # NOTE: When updating to version 42, change this to /42/
+  url_latest_version: /41/
 
   # Menu title if your navbar has a versions selector to access old versions 
of your site.
   # This menu appears only if you have at least one [params.versions] set.
@@ -269,11 +266,6 @@ params:
   # Set this flag to "true" if you want to display the banner.
   archived_version: true
 
-  # The version number for the version of the docs represented in this doc set.
-  # Used in the "version-banner" partial to display a version number for the
-  # current doc set.
-  # NOTE: This is automatically set from latest_version_number parameter above
-  version: 4.1
   versions:
     # NOTE: When adding version 42, update params.latest_version to "42" 
     # and params.latest_version_number to "4.2" at the top
@@ -372,11 +364,26 @@ params:
     - version: "0.7"
       url: /07/
       archived_version: true
+  
+  # Favicon configuration
+  favicon: /images/apache.png
+  
+  print: 
+    disable_toc: false
+  taxonomy:
+    # set taxonomyCloud = [] to hide taxonomy clouds
+    taxonomyCloud: [tags, categories]
 
-  # A link to latest version of the docs. Used in the "version-banner" partial 
to
-  # point people to the main doc site.
-  # NOTE: When updating to version 42, change this to /42/
-  url_latest_version: /41/
+    # If used, must have same length as taxonomyCloud
+    taxonomyCloudTitle: [Tag Cloud, Categories]
+
+    # set taxonomyPageHeader = [] to hide taxonomies on the page headers
+    taxonomyPageHeader: [tags, categories]
+
+  privacy_policy: 
https://privacy.apache.org/policies/privacy-policy-public.html
+
+  # First one is picked as the Twitter card image if not set on page.
+  # images: [images/project-illustration.png]
 
   # Repository configuration (URLs for in-page links to opening issues and 
suggesting changes)
   github_repo: https://github.com/apache/kafka-site/
diff --git a/layouts/partials/hooks/body-end.html 
b/layouts/partials/hooks/body-end.html
index ad3c4546f..db82fc8e3 100644
--- a/layouts/partials/hooks/body-end.html
+++ b/layouts/partials/hooks/body-end.html
@@ -8,15 +8,15 @@
 
 {{ $stickySmart := resources.Get "js/sticky-smart.js" }}
 {{ if $stickySmart }}
-<script src="{{ $stickySmart.Permalink }}"></script>
+<script src="{{ $stickySmart.RelPermalink }}"></script>
 {{ end }}
 
 {{ $sidebarToggle := resources.Get "js/sidebar-toggle.js" }}
 {{ if $sidebarToggle }}
-<script src="{{ $sidebarToggle.Permalink }}"></script>
+<script src="{{ $sidebarToggle.RelPermalink }}"></script>
 {{ end }}
 
 {{ $tocScrollspy := resources.Get "js/toc-scrollspy.js" }}
 {{ if $tocScrollspy }}
-<script src="{{ $tocScrollspy.Permalink }}"></script>
+<script src="{{ $tocScrollspy.RelPermalink }}"></script>
 {{ end }}
\ No newline at end of file
diff --git a/layouts/shortcodes/doc-redirect.html 
b/layouts/shortcodes/doc-redirect.html
new file mode 100644
index 000000000..eadd699b1
--- /dev/null
+++ b/layouts/shortcodes/doc-redirect.html
@@ -0,0 +1,117 @@
+<script>
+    (function () {
+        // 1. Get version from Hugo config
+        var latestVersion = "{{ .Site.Params.latest_version }}";
+
+        // 2. Define the Mapping
+        var hashIncludedMap = {
+            //Getting Started menu
+            "#gettingStarted": "getting-started",
+            "#introduction": "getting-started/introduction",
+            "#uses": "getting-started/uses",
+            "#quickstart": "getting-started/quickstart",
+            "#ecosystem": "getting-started/ecosystem",
+            "#upgrade": "getting-started/upgrade",
+            "#zk2kraft-summary": "getting-started/zk2kraft",
+            "#compatibility-summary": "getting-started/compatibility",
+            "#docker": "getting-started/docker",
+
+            //APIs menu
+            "#api": "apis",
+            "#producerapi": "apis/#producer-api",
+            "#consumerapi": "apis/#consumer-api",
+            "#shareconsumerapi": "apis/#share-consumer-api",
+            "#streamsapi": "apis/#streams-api",
+            "#connectapi": "apis/#connect-api",
+            "#adminapi": "apis/#admin-api",
+
+            //Configuration menu
+            "#configuration": "configuration",
+            "#brokerconfigs": "configuration/broker-configs/",
+            "#topicconfigs": "configuration/topic-configs/",
+            "#groupconfigs": "configuration/group-configs/",
+            "#producerconfigs": "configuration/producer-configs/",
+            "#consumerconfigs": "configuration/consumer-configs/",
+            "#connectconfigs": "configuration/kafka-connect-configs/",
+            "#sourceconnectconfigs": 
"configuration/kafka-connect-configs/#source-connector-configs",
+            "#sinkconnectconfigs": 
"configuration/kafka-connect-configs/#sink-connector-configs",
+            "#streamsconfigs": "configuration/kafka-streams-configs/",
+            "#adminclientconfigs": "configuration/admin-configs/",
+            "#mirrormakerconfigs": "configuration/mirrormaker-configs/",
+            "#systemproperties": "configuration/system-properties/",
+            "#tieredstorageconfigs": "configuration/tiered-storage-configs/",
+            "#config_providers": "configuration/configuration-providers/",
+
+            //Design menu
+            "#design": "design/design/",
+            "#majordesignelements": "design/design/#motivation",
+            "#persistence": "design/design/#persistence",
+            "#maximizingefficiency": "design/design/#efficiency",
+            "#theproducer": "design/design/#the-producer",
+            "#theconsumer": "design/design/#the-consumer",
+            "#semantics": "design/design/#message-delivery-semantics",
+            "#usingtransactions": "design/design/#using-transactions",
+            "#sharegroups": "design/design/#share-groups",
+            "#replication": "design/design/#replication",
+            "#compaction": "design/design/#log-compaction",
+            "#design_quotas": "design/design/#quotas",
+
+            //Implementation menu
+            "#implementation": "implementation",
+            "#networklayer": "implementation/network-layer/",
+            "#messages": "implementation/messages/",
+            "#messageformat": "implementation/message-format/",
+            "#log": "implementation/log/",
+            "#distributionimpl": "implementation/distribution/",
+
+            //Operations menu
+            "#operations": "operations",
+            "#basic_ops": "operations/basic-kafka-operations/",
+            "#datacenters": "operations/datacenters/",
+            "#georeplication": 
"operations/geo-replication-cross-cluster-data-mirroring/",
+            "#multitenancy": "operations/multi-tenancy/",
+            "#java": "operations/java-version/",
+            "#hwandos": "operations/hardware-and-os/",
+            "#monitoring": "operations/monitoring/",
+            "#kraft": "operations/kraft/",
+            "#tiered_storage": "operations/tiered-storage/",
+            "#consumer_rebalance_protocol": 
"operations/consumer-rebalance-protocol/",
+            "#transaction_protocol": "operations/transaction-protocol/",
+            "#eligible_leader_replicas": 
"operations/eligible-leader-replicas/",
+
+            //Security menu
+            "#security": "security",
+            "#security_overview": "security/security-overview/",
+            "#listener_configuration": "security/listener-configuration/",
+            "#security_ssl": 
"security/encryption-and-authentication-using-ssl/",
+            "#security_sasl": "security/authentication-using-sasl/",
+            "#security_authz": "security/authorization-and-acls/",
+            "#security_rolling_upgrade": 
"security/incorporating-security-features-in-a-running-cluster/",
+
+            //Connect menu
+            "#connect": "kafka-connect",
+            "#connect_overview": "kafka-connect/overview/",
+            "#connect_user": "kafka-connect/user-guide/",
+            "#connect_development": 
"kafka-connect/connector-development-guide/",
+            "#connect_administration": "kafka-connect/administration/",
+
+            //Streams menu
+            "#streams": "streams"
+
+        };
+
+        var currentHash = window.location.hash;
+
+        // 3. Logic:
+        // IF hash matches map -> Go to Deep Link
+        // ELSE -> Go to legitimate Docs Homepage 
+
+        if (currentHash && hashIncludedMap[currentHash]) {
+            window.location.replace("/" + latestVersion + "/" + 
hashIncludedMap[currentHash]);
+        } else {
+            // Default Fallback 
+            window.location.replace("/" + latestVersion + 
"/getting-started/introduction");
+        }
+    })();
+</script>
+<p>Redirecting...</p>
\ No newline at end of file
diff --git a/layouts/shortcodes/include-latest.html 
b/layouts/shortcodes/include-latest.html
new file mode 100644
index 000000000..4a7b87b2f
--- /dev/null
+++ b/layouts/shortcodes/include-latest.html
@@ -0,0 +1,8 @@
+{{- $path := .Get "path" -}}
+{{- $latestVersion := site.Params.latest_version -}}
+{{- $fullPath := printf "/%s/%s" $latestVersion $path -}}
+{{- with site.GetPage $fullPath -}}
+{{ .Content }}
+{{- else -}}
+{{ errorf "Unable to find page at %q" $fullPath }}
+{{- end -}}
\ No newline at end of file
diff --git a/layouts/shortcodes/version-redirect.html 
b/layouts/shortcodes/version-redirect.html
new file mode 100644
index 000000000..377419d68
--- /dev/null
+++ b/layouts/shortcodes/version-redirect.html
@@ -0,0 +1,11 @@
+{{/*
+Shortcode: version-redirect
+Description: Redirects to a path prefixed with the site's latest_version 
parameter.
+Usage: {{< version-redirect path="design/protocol">}}
+    */}}
+    {{ $latest := site.Params.latest_version }}
+    {{ $path := .Get "path" }}
+    <script>
+        window.location.href = "\/" + "{{ $latest }}" + "\/" + "{{ $path }}";
+    </script>
+    <p>Redirecting to <a href="/{{ $latest }}/{{ $path }}">/{{ $latest }}/{{ 
$path }}</a>...</p>
\ No newline at end of file
diff --git a/layouts/simple/single.html b/layouts/simple/single.html
new file mode 100644
index 000000000..e9433a943
--- /dev/null
+++ b/layouts/simple/single.html
@@ -0,0 +1,31 @@
+{{ define "main" }}
+<section class="section">
+    <div class="container py-4" style="margin-top: 1rem;">
+        <div class="row justify-content-center">
+            <div class="col-lg-10">
+                <h1 class="mb-4">{{ .Title }}</h1>
+                <div class="td-content">
+                    <style>
+                        /* Force content to take full width of container */
+                        .td-content {
+                            max-width: 100%;
+                        }
+
+                        /* Force all children (paragraphs, headers) to fill 
the container */
+                        .td-content>* {
+                            max-width: 100% !important;
+                        }
+
+                        /* Responsive images */
+                        .td-content img {
+                            max-width: 100%;
+                            height: auto;
+                        }
+                    </style>
+                    {{ .Content }}
+                </div>
+            </div>
+        </div>
+    </div>
+</section>
+{{ end }}
\ No newline at end of file
diff --git a/scripts/verify_redirects.py b/scripts/verify_redirects.py
new file mode 100644
index 000000000..b279783ee
--- /dev/null
+++ b/scripts/verify_redirects.py
@@ -0,0 +1,103 @@
+import re
+import sys
+import argparse
+from urllib.request import urlopen
+from urllib.error import HTTPError, URLError
+from urllib.parse import urljoin
+
+REDIRECT_FILE = '../layouts/shortcodes/doc-redirect.html'
+BASE_URL = 'http://localhost:1313/41/'
+
+def parse_redirects(file_path):
+    """Parses the JS object map from the shortcode file."""
+    try:
+        with open(file_path, 'r') as f:
+            content = f.read()
+    except FileNotFoundError:
+        print(f"Error: File {file_path} not found.")
+        sys.exit(1)
+    
+    match = re.search(r'var hashIncludedMap = \{([\s\S]*?)\};', content)
+    if not match:
+        print(f"Error: Could not find hashIncludedMap in {file_path}")
+        return {}
+
+    map_content = match.group(1)
+    redirects = {}
+    
+    # Regex to find key-value pairs: "#key": "value"
+    pattern = re.compile(r'"(#[^"]+)":\s*"([^"]+)"')
+    
+    for line in map_content.splitlines():
+        m = pattern.search(line)
+        if m:
+            redirects[m.group(1)] = m.group(2)
+            
+    return redirects
+
+def check_url(target_path):
+    """
+    Checks if the target path exists on the server using urllib.
+    Returns (status_code, anchor_found, error_msg)
+    """
+    path_part = target_path
+    anchor = None
+    if '#' in target_path:
+        path_part, anchor = target_path.split('#', 1)
+
+    url = urljoin(BASE_URL, path_part)
+    
+    try:
+        with urlopen(url, timeout=2) as response:
+            status = response.getcode()
+            content = response.read().decode('utf-8', errors='ignore')
+            
+            if anchor:
+                # Naive check for anchor in HTML
+                if f'id="{anchor}"' in content or f'name="{anchor}"' in 
content:
+                    return status, True, None
+                else:
+                    return status, False, f"Anchor #{anchor} not found in page"
+            
+            return status, True, None
+
+    except HTTPError as e:
+        return e.code, False, f"HTTP {e.code}"
+    except URLError as e:
+        return 0, False, f"Connection Failed: {e.reason}"
+    except Exception as e:
+        return 0, False, str(e)
+
+def main():
+    print(f"Parsing {REDIRECT_FILE}...")
+    redirects = parse_redirects(REDIRECT_FILE)
+    print(f"Found {len(redirects)} redirects.")
+    
+    print(f"Checking against {BASE_URL}...")
+    
+    success_count = 0
+    fail_count = 0
+    warning_count = 0 
+
+    for key, target in redirects.items():
+        status, anchor_ok, msg = check_url(target)
+        
+        if status == 200:
+            if anchor_ok:
+                print(f"[PASS] {key} -> {target}")
+                success_count += 1
+            else:
+                print(f"[WARN] {key} -> {target} (Page OK, {msg})")
+                warning_count += 1
+        else:
+            print(f"[FAIL] {key} -> {target} ({msg})")
+            fail_count += 1
+            
+    print("-" * 30)
+    print(f"Summary: PASS={success_count}, WARN={warning_count}, 
FAIL={fail_count}")
+    
+    if fail_count > 0:
+        sys.exit(1)
+
+if __name__ == "__main__":
+    main()

Reply via email to