@@ -280,6 +280,18 @@ def _get_global_context(self) -> dict[str, Any]:
280280 context .update (self .sidebar_config )
281281 return context
282282
283+ def _canonical_url_for_path (self , output_path : Path ) -> str :
284+ """Compute the fully-qualified canonical URL for a given output file path.
285+
286+ Args:
287+ output_path: Absolute path to the output file within the output directory.
288+
289+ Returns:
290+ The fully-qualified canonical URL for the given file.
291+ """
292+ relative = output_path .relative_to (self .output_dir )
293+ return f"{ self .site_url } /{ relative .as_posix ()} "
294+
283295 def generate (self , include_drafts : bool = False ) -> None :
284296 """Generate the complete static site.
285297
@@ -553,8 +565,6 @@ def _generate_post_page(
553565 context ["prev_post" ] = None
554566 context ["next_post" ] = None
555567
556- html = self .renderer .render_post (post , ** context )
557-
558568 # Determine output path based on date
559569 if post .date :
560570 # Create year/month/day directory structure
@@ -573,17 +583,19 @@ def _generate_post_page(
573583 # Fallback for posts without dates
574584 output_path = self .output_dir / f"{ post .slug } .html"
575585
586+ context ["canonical_url" ] = self ._canonical_url_for_path (output_path )
587+ html = self .renderer .render_post (post , ** context )
576588 output_path .write_text (html , encoding = "utf-8" )
577589
578590 def _generate_page (self , page : Page , pages : list [Page ]) -> None :
579591 """Generate a single static page."""
580592 context = self ._get_global_context ()
581593 context ["pages" ] = pages
594+ output_path = self .output_dir / f"{ page .slug } .html"
595+ context ["canonical_url" ] = self ._canonical_url_for_path (output_path )
582596
583597 html = self .renderer .render_page (page , ** context )
584598
585- # Output to root of site
586- output_path = self .output_dir / f"{ page .slug } .html"
587599 output_path .write_text (html , encoding = "utf-8" )
588600
589601 def _generate_index_page (self , posts : list [Post ], pages : list [Page ]) -> None :
@@ -600,10 +612,6 @@ def _generate_index_page(self, posts: list[Post], pages: list[Page]) -> None:
600612
601613 # Generate each page
602614 for page_num , page_posts in enumerate (paginated_posts , start = 1 ):
603- html = self .renderer .render_index (
604- page_posts , page = page_num , total_pages = total_pages , ** context
605- )
606-
607615 if page_num == 1 :
608616 # First page is at root
609617 output_path = self .output_dir / "index.html"
@@ -613,16 +621,22 @@ def _generate_index_page(self, posts: list[Post], pages: list[Page]) -> None:
613621 page_dir .mkdir (exist_ok = True )
614622 output_path = page_dir / f"{ page_num } .html"
615623
624+ context ["canonical_url" ] = self ._canonical_url_for_path (output_path )
625+ html = self .renderer .render_index (
626+ page_posts , page = page_num , total_pages = total_pages , ** context
627+ )
628+
616629 output_path .write_text (html , encoding = "utf-8" )
617630
618631 def _generate_archive_page (self , posts : list [Post ], pages : list [Page ]) -> None :
619632 """Generate the archive page."""
620633 context = self ._get_global_context ()
621634 context ["pages" ] = pages
635+ output_path = self .output_dir / "archive.html"
636+ context ["canonical_url" ] = self ._canonical_url_for_path (output_path )
622637 html = self .renderer .render_archive (
623638 posts , page = 1 , total_pages = 1 , base_path = "/archive" , ** context
624639 )
625- output_path = self .output_dir / "archive.html"
626640 output_path .write_text (html , encoding = "utf-8" )
627641
628642 def _generate_date_archives (self , posts : list [Post ], pages : list [Page ]) -> None :
@@ -659,15 +673,6 @@ def _generate_date_archives(self, posts: list[Post], pages: list[Page]) -> None:
659673 # Base path for pagination links
660674 base_path = f"/{ year } "
661675
662- html = self .renderer .render_archive (
663- page_posts ,
664- archive_title = f"Posts from { year } " ,
665- page = page_num ,
666- total_pages = total_pages ,
667- base_path = base_path ,
668- ** context ,
669- )
670-
671676 if page_num == 1 :
672677 # First page is at year/index.html
673678 output_path = year_dir / "index.html"
@@ -677,6 +682,16 @@ def _generate_date_archives(self, posts: list[Post], pages: list[Page]) -> None:
677682 page_dir .mkdir (exist_ok = True )
678683 output_path = page_dir / f"{ page_num } .html"
679684
685+ context ["canonical_url" ] = self ._canonical_url_for_path (output_path )
686+ html = self .renderer .render_archive (
687+ page_posts ,
688+ archive_title = f"Posts from { year } " ,
689+ page = page_num ,
690+ total_pages = total_pages ,
691+ base_path = base_path ,
692+ ** context ,
693+ )
694+
680695 output_path .write_text (html , encoding = "utf-8" )
681696
682697 # Generate month archives with pagination
@@ -695,15 +710,6 @@ def _generate_date_archives(self, posts: list[Post], pages: list[Page]) -> None:
695710 # Base path for pagination links
696711 base_path = f"/{ year } /{ month :02d} "
697712
698- html = self .renderer .render_archive (
699- page_posts ,
700- archive_title = f"Posts from { month_name } " ,
701- page = page_num ,
702- total_pages = total_pages ,
703- base_path = base_path ,
704- ** context ,
705- )
706-
707713 if page_num == 1 :
708714 # First page is at year/month/index.html
709715 output_path = month_dir / "index.html"
@@ -713,6 +719,16 @@ def _generate_date_archives(self, posts: list[Post], pages: list[Page]) -> None:
713719 page_dir .mkdir (exist_ok = True )
714720 output_path = page_dir / f"{ page_num } .html"
715721
722+ context ["canonical_url" ] = self ._canonical_url_for_path (output_path )
723+ html = self .renderer .render_archive (
724+ page_posts ,
725+ archive_title = f"Posts from { month_name } " ,
726+ page = page_num ,
727+ total_pages = total_pages ,
728+ base_path = base_path ,
729+ ** context ,
730+ )
731+
716732 output_path .write_text (html , encoding = "utf-8" )
717733
718734 # Generate day archives with pagination
@@ -731,15 +747,6 @@ def _generate_date_archives(self, posts: list[Post], pages: list[Page]) -> None:
731747 # Base path for pagination links
732748 base_path = f"/{ year } /{ month :02d} /{ day :02d} "
733749
734- html = self .renderer .render_archive (
735- page_posts ,
736- archive_title = f"Posts from { date_str } " ,
737- page = page_num ,
738- total_pages = total_pages ,
739- base_path = base_path ,
740- ** context ,
741- )
742-
743750 if page_num == 1 :
744751 # First page is at year/month/day/index.html
745752 output_path = day_dir / "index.html"
@@ -749,6 +756,16 @@ def _generate_date_archives(self, posts: list[Post], pages: list[Page]) -> None:
749756 page_dir .mkdir (exist_ok = True )
750757 output_path = page_dir / f"{ page_num } .html"
751758
759+ context ["canonical_url" ] = self ._canonical_url_for_path (output_path )
760+ html = self .renderer .render_archive (
761+ page_posts ,
762+ archive_title = f"Posts from { date_str } " ,
763+ page = page_num ,
764+ total_pages = total_pages ,
765+ base_path = base_path ,
766+ ** context ,
767+ )
768+
752769 output_path .write_text (html , encoding = "utf-8" )
753770
754771 def _generate_tag_pages (self , posts : list [Post ], pages : list [Page ]) -> None :
@@ -786,15 +803,6 @@ def get_sort_key(post: Post) -> float:
786803
787804 # Generate each page
788805 for page_num , page_posts in enumerate (paginated_posts , start = 1 ):
789- html = self .renderer .render_tag_page (
790- tag_display , # Use display name for rendering
791- page_posts ,
792- page = page_num ,
793- total_pages = total_pages ,
794- safe_tag = safe_tag ,
795- ** context ,
796- )
797-
798806 if page_num == 1 :
799807 # First page is at tag/{tag}.html
800808 output_path = tag_dir / f"{ safe_tag } .html"
@@ -804,6 +812,16 @@ def get_sort_key(post: Post) -> float:
804812 tag_page_dir .mkdir (exist_ok = True )
805813 output_path = tag_page_dir / f"{ page_num } .html"
806814
815+ context ["canonical_url" ] = self ._canonical_url_for_path (output_path )
816+ html = self .renderer .render_tag_page (
817+ tag_display , # Use display name for rendering
818+ page_posts ,
819+ page = page_num ,
820+ total_pages = total_pages ,
821+ safe_tag = safe_tag ,
822+ ** context ,
823+ )
824+
807825 output_path .write_text (html , encoding = "utf-8" )
808826
809827 def _group_posts_by_tag (
@@ -886,11 +904,11 @@ def _generate_tags_page(self, posts: list[Post], pages: list[Page]) -> None:
886904 # Render the tags page
887905 context = self ._get_global_context ()
888906 context ["pages" ] = pages
907+ output_path = self .output_dir / "tags.html"
908+ context ["canonical_url" ] = self ._canonical_url_for_path (output_path )
889909
890910 html = self .renderer .render_tags_page (tag_data , ** context )
891911
892- # Output to root of site
893- output_path = self .output_dir / "tags.html"
894912 output_path .write_text (html , encoding = "utf-8" )
895913
896914 def _generate_categories_page (self , posts : list [Post ], pages : list [Page ]) -> None :
@@ -954,11 +972,11 @@ def _generate_categories_page(self, posts: list[Post], pages: list[Page]) -> Non
954972 # Render the categories page
955973 context = self ._get_global_context ()
956974 context ["pages" ] = pages
975+ output_path = self .output_dir / "categories.html"
976+ context ["canonical_url" ] = self ._canonical_url_for_path (output_path )
957977
958978 html = self .renderer .render_categories_page (category_data , ** context )
959979
960- # Output to root of site
961- output_path = self .output_dir / "categories.html"
962980 output_path .write_text (html , encoding = "utf-8" )
963981
964982 def _generate_category_pages (self , posts : list [Post ], pages : list [Page ]) -> None :
@@ -1001,15 +1019,6 @@ def get_sort_key(post: Post) -> float:
10011019
10021020 # Generate each page
10031021 for page_num , page_posts in enumerate (paginated_posts , start = 1 ):
1004- html = self .renderer .render_category_page (
1005- category_display , # Use display name for rendering
1006- page_posts ,
1007- page = page_num ,
1008- total_pages = total_pages ,
1009- safe_category = safe_category ,
1010- ** context ,
1011- )
1012-
10131022 if page_num == 1 :
10141023 # First page is at category/{category}.html
10151024 output_path = category_dir / f"{ safe_category } .html"
@@ -1019,6 +1028,16 @@ def get_sort_key(post: Post) -> float:
10191028 category_page_dir .mkdir (exist_ok = True )
10201029 output_path = category_page_dir / f"{ page_num } .html"
10211030
1031+ context ["canonical_url" ] = self ._canonical_url_for_path (output_path )
1032+ html = self .renderer .render_category_page (
1033+ category_display , # Use display name for rendering
1034+ page_posts ,
1035+ page = page_num ,
1036+ total_pages = total_pages ,
1037+ safe_category = safe_category ,
1038+ ** context ,
1039+ )
1040+
10221041 output_path .write_text (html , encoding = "utf-8" )
10231042
10241043 def _group_posts_by_category (
@@ -1093,8 +1112,9 @@ def _generate_search_page(self, pages: list[Page]) -> None:
10931112 """
10941113 context = self ._get_global_context ()
10951114 context ["pages" ] = pages
1096- html = self .renderer .render_search_page (** context )
10971115 output_path = self .output_dir / "search.html"
1116+ context ["canonical_url" ] = self ._canonical_url_for_path (output_path )
1117+ html = self .renderer .render_search_page (** context )
10981118 output_path .write_text (html , encoding = "utf-8" )
10991119
11001120 def _remove_stale_search_files (self ) -> None :
0 commit comments