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
Resolving PHP "could not find driver" Error: Ultimate Guide to Missing PDO Database Drivers
Duration: 00:00 | DP | 2026-07-04 08:03:00VS Code PHP Guide: How to Trace Function Definitions Like PHPStorm
Duration: 00:00 | DP | 2026-07-04 20:27:00Resolving Nginx Permission Denied (13) Errors for WebP Images Generated by PHP Imagick
Duration: 00:00 | DP | 2026-07-05 21:17:00Fixing Nginx 500 Error: Internal Redirection Cycle (SPA vs PHP Config)
Duration: 00:00 | DP | 2026-07-02 21:45:50Stop Making Timezone Mistakes in PHP: The Ultimate Guide to time() and UTC
Duration: 00:00 | DP | 2026-06-25 11:29:00Beyond 99.9%: A Deep Dive into a User-Centric Weighted Sampling Algorithm for Availability
Duration: 00:00 | DP | 2026-06-26 12:57:00PHP 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:40Boost Your VS Code Productivity: Select All Occurrences in a Single Keystroke!
Duration: 00:00 | DP | 2026-06-27 14:25:00The 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:33Recommended
Stop Mixing Code and User Uploads! The Ultimate Guide to a Secure and Scalable PHP MVC Project Structure
00:00 | 110When building a PHP MVC project, correctly handlin...
Beyond 99.9%: A Deep Dive into a User-Centric Weighted Sampling Algorithm for Availability
00:00 | 14Traditional availability calculation (success/tota...
Master cURL Timeouts: A Definitive Guide to Fixing "Operation timed out" Errors
00:00 | 142Frequently encountering "cURL Error: Operation tim...
Mastering Bootstrap 5 Rounded Corners: The Ultimate Guide to Border-Radius
00:00 | 137Struggling with border-radius in Bootstrap 5? This...