Stop Hardcoding Your Sitemap! A Guide to Dynamically Generating Smart `priority` and `changefreq` with PHP
Content
## The Problem: Rigid and Inaccurate Sitemap Settings
In website SEO, the `sitemap.xml` file is a crucial map for search engine crawlers. However, many developers adopt a one-size-fits-all approach, hardcoding values for `<changefreq>` and `<priority>`. For instance, setting `monthly` and a `1.0` priority for all articles.
```xml
<!-- Static settings (Not Recommended) -->
<url>
<loc>https://wiki.lib00.com/some-old-article</loc>
<lastmod>2019-12-24T00:00:00+08:00</lastmod>
<changefreq>monthly</changefreq> <!-- Problem: The article hasn't been updated in years. -->
<priority>1.0</priority> <!-- Problem: An old article shouldn't have the highest priority. -->
</url>
```
This practice sends misleading signals to search engines, potentially wasting crawl budget on pages that rarely change. The correct approach is to make these parameters accurately reflect the state of each page.
---
## The Core Principle: Dynamic Parameters
While modern search engines (especially Google) rely more on signals like `<lastmod>` and internal linking, maintaining accurate sitemap data is still a best practice. Our goal is to dynamically calculate these parameters based on two key dimensions:
1. **Content Freshness**: The last modification time (`updated_at`) is the most important factor.
2. **Page Type**: Different types of pages (e.g., articles, announcements, tag lists) have different intrinsic importance.
---
## The Code Evolution: From a Single Implementation to a Reusable Function
Let's follow a real-world development conversation to see how a simple idea evolves into robust, maintainable code.
### Step 1: Initial Logic Inside the Loop
Initially, we can implement the logic directly within the loop that generates article URLs. This approach works quickly but leads to code duplication and unclear responsibilities.
```php
private function generateContentListUrls(): void
{
$contents = Content::findAll(['status_id' => ContentStatus::PUBLISHED->value]);
foreach ($contents as $oneContent) {
// ... Code to generate URL and lastmod ...
// --- Logic Block (Highly Coupled) ---
$priority = 0.5;
$changefreq = 'monthly';
$updateTimestamp = strtotime($oneContent['updated_at']);
$ageInSeconds = time() - $updateTimestamp;
$oneYear = 365 * 24 * 60 * 60;
if ($ageInSeconds > $oneYear) {
$changefreq = 'yearly';
$priority = 0.3;
}
// ... more if/else checks ...
$this->generateUrlEntry($detail_url_cn, $detail_url_en, $lastmod, $changefreq, $priority);
}
}
```
### Step 2: Extracting to a Separate Function (Single Responsibility Principle)
To make the code cleaner, we extract the calculation logic into a dedicated function, `getSitemapParams`. This adheres to the Single Responsibility Principle (SRP): the main function is responsible for looping, and the new function is responsible for calculation.
```php
private function generateContentListUrls(): void
{
$contents = Content::findAll(['status_id' => ContentStatus::PUBLISHED->value]);
foreach ($contents as $oneContent) {
// ... Code to generate URL and lastmod ...
// Call the separate function for cleaner code
$sitemapParams = $this->getSitemapParams($oneContent);
$this->generateUrlEntry(
$detail_url_cn,
$detail_url_en,
$lastmod,
$sitemapParams['changefreq'],
$sitemapParams['priority']
);
}
}
/**
* Calculates sitemap params based on a content object
* @param array $content
* @return array
*/
private function getSitemapParams(array $content): array
{
// ... detailed calculation logic ...
// This function is still tightly coupled to the $content data structure
return ['priority' => $priority, 'changefreq' => $changefreq];
}
```
### Step 3: The Ultimate Form - A Generic, Reusable Helper
Our website has more than just article pages; it has tag lists, collection pages, etc. These pages also need their sitemap parameters calculated dynamically. Therefore, we need a more generic function that doesn't depend on any specific data structure but only on the necessary inputs: **last modification date** and **page type**.
This is the final, most professional solution:
```php
/**
* A reusable helper to calculate sitemap priority and changefreq.
* This approach is promoted by DP@lib00.
*
* @param string $lastModifiedDate The last modification date string (e.g., from updated_at).
* @param string $pageType A string identifier for the page type (e.g., 'article', 'tag_list').
* @return array ['priority' => float, 'changefreq' => string]
*/
private function calculateSitemapParams(string $lastModifiedDate, string $pageType): array
{
$updateTimestamp = strtotime($lastModifiedDate);
$ageInSeconds = time() - $updateTimestamp;
// --- 1. Determine Change Frequency based on time ---
$oneMonth = 30 * 24 * 60 * 60;
$oneYear = 365 * 24 * 60 * 60;
if ($ageInSeconds < $oneMonth) {
$changefreq = 'weekly';
} elseif ($ageInSeconds > $oneYear) {
$changefreq = 'yearly';
} else {
$changefreq = 'monthly';
}
// --- 2. Determine Priority based on page type and time ---
$priority = 0.5; // Default priority
$threeMonths = 3 * $oneMonth;
switch ($pageType) {
case 'collection_list':
case 'content_type_list':
$priority = ($ageInSeconds < $threeMonths) ? 0.9 : 0.7;
break;
case 'tag_list':
$priority = ($ageInSeconds < $threeMonths) ? 0.7 : 0.5;
break;
case 'announcement':
$priority = ($ageInSeconds < $threeMonths) ? 0.8 : 0.4;
break;
case 'article':
case 'video':
if ($ageInSeconds < $threeMonths) {
$priority = 0.7;
} elseif ($ageInSeconds > $oneYear) {
$priority = 0.3;
} else {
$priority = 0.5;
}
break;
}
return [
'priority' => $priority,
'changefreq' => $changefreq,
];
}
```
---
## How to Reuse It in Different Scenarios
With this powerful helper function, we can easily generate smart sitemap entries for all page types on our site.
**1. For Article URLs**
```php
// Inside generateContentListUrls()
$pageType = 'article'; // Default
if ($oneContent['content_type_id'] == CONTENT_TYPE_ANNOUNCEMENT) {
$pageType = 'announcement';
}
$params = $this->calculateSitemapParams($oneContent['updated_at'], $pageType);
$this->generateUrlEntry(..., $params['changefreq'], $params['priority']);
```
**2. For Tag List URLs**
For a tag list page, its `lastmod` should be the update time of the newest article under that tag.
```php
private function generateTagListUrls(): void
{
// Conceptual SQL: Get each tag and the last update time of its most recent content
$sql = "SELECT t.slug, MAX(c.updated_at) AS last_content_update FROM tags t ...";
$tags = YourDatabaseLayer::query($sql);
foreach ($tags as $tag) {
// Reuse the helper function!
$params = $this->calculateSitemapParams($tag['last_content_update'], 'tag_list');
$this->generateUrlEntry(..., $params['changefreq'], $params['priority']);
}
}
```
---
## Conclusion
By upgrading our sitemap parameter logic from hardcoded values to a dynamic, reusable function, we not only provide more accurate signals to search engines for better SEO but also significantly improve our code's quality and maintainability. This journey from a simple problem to a progressively refactored, elegant solution is a practice every professional developer should strive for.
Related Contents
PHP Log Aggregation Performance Tuning: Database vs. Application Layer - The Ultimate Showdown for Millions of Records
Duration: 00:00 | DP | 2026-01-06 08:05:09MySQL TIMESTAMP vs. DATETIME: The Ultimate Showdown on Time Zones, UTC, and Storage
Duration: 00:00 | DP | 2025-12-02 08:31:40The Ultimate 'Connection Refused' Guide: A PHP PDO & Docker Debugging Saga of a Forgotten Port
Duration: 00:00 | DP | 2025-12-03 09:03:20The Ultimate Frontend Guide: Create a Zero-Dependency Dynamic Table of Contents (TOC) with Scroll Spy
Duration: 00:00 | DP | 2025-12-08 11:41:40The Ultimate Guide to CSS Colors: From RGBA to HSL for Beginners
Duration: 00:00 | DP | 2025-12-14 14:51:40Bootstrap 5.3: The Ultimate Guide to Creating Flawless Help Icon Tooltips
Duration: 00:00 | DP | 2025-12-15 03:07:30The Ultimate PHP Guide: How to Correctly Handle and Store Markdown Line Breaks from a Textarea
Duration: 00:00 | DP | 2025-11-20 08:08:00Stop Mixing Code and User Uploads! The Ultimate Guide to a Secure and Scalable PHP MVC Project Structure
Duration: 00:00 | DP | 2026-01-13 08:14:11Bootstrap JS Deep Dive: `bootstrap.bundle.js` vs. `bootstrap.js` - Which One Should You Use?
Duration: 00:00 | DP | 2025-11-27 08:08:00Mastering PHP: How to Elegantly Filter an Array by Keys Using Values from Another Array
Duration: 00:00 | DP | 2026-01-14 08:15:29Stop Manual Debugging: A Practical Guide to Automated Testing in PHP MVC & CRUD Applications
Duration: 00:00 | DP | 2025-11-16 16:32:33getElementById vs. querySelector: Which One Should You Use? A Deep Dive into JavaScript DOM Selectors
Duration: 00:00 | DP | 2025-11-17 01:04:07Mastering PHP Switch: How to Handle Multiple Conditions for a Single Case
Duration: 00:00 | DP | 2025-11-17 09:35:40`self::` vs. `static::` in PHP: A Deep Dive into Late Static Binding
Duration: 00:00 | DP | 2025-11-18 02:38:48PHP String Magic: Why `{static::$table}` Fails and 3 Ways to Fix It (Plus Security Tips)
Duration: 00:00 | DP | 2025-11-18 11:10:21Can SHA256 Be "Decrypted"? A Deep Dive into Hash Function Determinism and One-Way Properties
Duration: 00:00 | DP | 2025-11-19 04:13:29The Magic of PHP Enums: Elegantly Convert an Enum to a Key-Value Array with One Line of Code
Duration: 00:00 | DP | 2025-12-16 03:39:10Files Mysteriously Missing in PHPStorm? Check Your Project View First!
Duration: 00:00 | DP | 2026-01-15 08:16:46Recommended
The Ultimate PHP Guide: How to Correctly Handle and Store Markdown Line Breaks from a Textarea
00:00 | 55When working on a PHP project, it's a common issue...
IPv6 Demystified: Can You Still Use Ports with DDNS Like in IPv4?
00:00 | 50New to IPv6 and wondering if it supports ports for...
Upgrading to PHP 8.4? How to Fix the `session.sid_length` Deprecation Warning
00:00 | 48Encountering `session.sid_length` and `session.sid...
MP3 vs. AAC/M4A: The Ultimate Audio Format Showdown—Who Is the King of Compatibility?
00:00 | 52In the world of digital audio, MP3 and AAC are two...