๐ฏ The Problem That Started It All
As a WordPress developer and AI SEO Specialist, I noticed a recurring pain point across numerous websites: internal linking is crucial for SEO, but it's incredibly tedious to maintain manually.
Imagine managing a blog with 1,000+ articles. Every time you publish new content, you should ideally:
- Find relevant older posts to link to
- Update older posts to link to the new content
- Maintain consistent anchor text
- Avoid over-optimization
- Track which pages link where
Doing this manually? Nearly impossible at scale.
Third-party tools exist, but they either:
- Are resource-heavy and crash shared hosting
- Lack granular control
- Have security concerns with external dependencies
- Cost prohibitive amounts monthly
So I decided: Let's build something better.
๐๏ธ Architecture & Technology Stack
Core Technologies
WordPress Version: 5.0+
PHP Version: 7.4+ (8.0+ recommended)
MySQL: 5.6+
JavaScript: Vanilla JS + jQuery (for admin)
CSS: Custom with responsive design
Why These Choices?
PHP 7.4+: Modern PHP features like typed properties and arrow functions made the code cleaner and more maintainable. Plus, better performance and security.
Vanilla JavaScript: For a plugin that needs to work across diverse WordPress installations, minimizing dependencies was crucial. No React, no Vueโjust clean, efficient JavaScript.
Custom Database Tables: Instead of using WordPress post meta (which can become unwieldy), I created dedicated tables for:
- Keywords management
- Link tracking
- Processing queue
- Trash/recovery system
๐จ Plugin Architecture: The Four Pillars
I structured the plugin around four core classes:
1. Database Manager (class-database-manager.php
)
class AIINLITO_Database_Manager {
private $keywords_table;
private $tracking_table;
private $queue_table;
private $trash_table;
public function create_tables() {
// Creates optimized tables with proper indexes
}
public function add_keyword($keyword, $target_url) {
// Validates, sanitizes, and stores keywords
}
// ... more methods
}
Key Decisions:
- Dedicated tables over post meta for performance
- Proper indexing on frequently queried columns
- Foreign key relationships for data integrity
- Prepared statements everywhere for security
2. Settings Manager (class-settings-manager.php
)
This handles all plugin configurations:
- Maximum keywords allowed
- Link density controls (links per 100 words)
- Post type selection
- Heading tag controls (H1-H6)
- Batch processing sizes
The Challenge: Making settings flexible enough for power users but simple enough for beginners.
Solution: Smart defaults with progressive disclosure. Basic settings upfront, advanced options hidden behind toggles.
3. Linking Engine (class-linking-engine.php
)
The heart of the plugin. This is where the magic happens:
class AIINLITO_Linking_Engine {
public function process_keyword_in_content($content, $keyword_data, $post) {
// 1. Check if keyword already has links
// 2. Calculate word count and max allowed links
// 3. Find keyword matches (case-insensitive search)
// 4. Preserve original text case
// 5. Insert link with tracking attributes
// 6. Update database
}
// ... more methods
}
Key Features Implemented:
Case-Preserving Matching: If the keyword is \"WordPress SEO\" but the text says \"wordpress seo\", it links the lowercase version without changing it.
-
Smart Link Placement:
- Only one link per keyword per page
- Respects user-defined link density
- Avoids linking inside existing links
- Can skip heading tags if configured
WordPress Block Editor Compatible: The engine parses Gutenberg block comments correctly, ensuring links aren't placed in the wrong locations.
4. Admin Manager (class-admin-manager.php
)
The user interface layer:
- AJAX handlers for real-time updates
- CSV import/export functionality
- Analytics dashboard
- Settings interface
- Trash management
โก Performance: The Biggest Challenge
Problem: Server Overload
Early prototypes had a major issue: processing thousands of posts would timeout or crash the server.
Solution 1: Background Processing with WordPress Cron
// Schedule recurring processing
wp_schedule_event(time(), 'hourly', 'aiinlito_process_keywords_cron');
// Process in batches
public function process_keywords_batch() {
$batch_size = $this->settings_manager->get_setting('batch_size', 10);
$queue_items = $this->db_manager->get_processing_batch($batch_size);
foreach ($queue_items as $item) {
// Process one item at a time
$this->process_single_item($item);
}
}
Key Insight: Don't try to process everything at once. Use a queue system and process items in small, manageable batches.
Solution 2: Smart Caching
Instead of hitting the database repeatedly:
- Cache keyword lists in memory during processing
- Use WordPress transients for frequently accessed data
- Implement object caching support
Solution 3: Processing Queue
CREATE TABLE processing_queue (
id int(11) NOT NULL AUTO_INCREMENT,
post_id int(11) NOT NULL,
keyword_id int(11) DEFAULT NULL,
status varchar(20) DEFAULT 'pending',
priority int(11) DEFAULT 0,
created_date datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY post_id (post_id),
KEY status (status),
KEY priority (priority)
)
This queue system allows:
- Prioritization: New posts get processed first
- Fault tolerance: Failed items can be retried
- Progress tracking: Users see real-time updates
- Scalability: Can process millions of posts over time
๐ Security: No Compromises
WordPress plugin security is critical. Here's what I implemented:
1. Nonce Verification on Every AJAX Call
public function ajax_add_keyword() {
// Verify nonce
$nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
if (!wp_verify_nonce($nonce, 'aiinlito_admin_nonce')) {
wp_die('Security check failed');
}
// Check capabilities
if (!current_user_can('manage_options')) {
wp_die('Insufficient permissions');
}
// Process request...
}
2. Input Sanitization & Validation
Every single input goes through:
-
sanitize_text_field()
for text -
esc_url_raw()
for URLs -
intval()
for integers -
esc_sql()
for database queries
3. Prepared Statements
// NEVER do this:
$wpdb->query(\"SELECT * FROM table WHERE id = $id\"); // โ SQL Injection risk
// ALWAYS do this:
$wpdb->prepare(\"SELECT * FROM table WHERE id = %d\", $id); // โ
Safe
4. CSRF Protection
All form submissions require valid nonces and capability checks.
5. External URL Blocking
private function is_external_url($url) {
$site_url = get_site_url();
$parsed_site = wp_parse_url($site_url);
$parsed_url = wp_parse_url($url);
return isset($parsed_url['host']) &&
$parsed_url['host'] !== $parsed_site['host'];
}
This prevents users from accidentally (or maliciously) adding external links.
๐ฏ Advanced Features Implementation
1. Smart Trash System (30-Day Recovery)
Instead of permanent deletion, keywords go to a trash table:
public function delete_keyword($keyword_id) {
// 1. Get keyword data
$keyword_data = $this->get_keyword($keyword_id);
// 2. Move to trash (expires in 30 days)
$expire_date = date('Y-m-d H:i:s', strtotime('+30 days'));
$this->wpdb->insert($this->trash_table, [
'original_keyword_id' => $keyword_data->id,
'keyword' => $keyword_data->keyword,
'target_url' => $keyword_data->target_url,
'expires_date' => $expire_date
]);
// 3. Remove all links from content
$this->remove_keyword_links($keyword_id);
// 4. Delete from active keywords
$this->wpdb->delete($this->keywords_table, ['id' => $keyword_id]);
}
Why This Matters: Users can safely delete keywords knowing they can restore them if needed. The automatic expiration keeps the database clean.
2. Bulk Import with CSV
private function import_from_csv($file_path) {
$imported = 0;
$duplicates = 0;
// Read CSV file using WordPress filesystem API
global $wp_filesystem;
WP_Filesystem();
$file_contents = $wp_filesystem->get_contents($file_path);
$lines = explode(\"
\", $file_contents);
foreach ($lines as $line) {
$data = str_getcsv($line);
$keyword = trim($data[0]);
$target_url = trim($data[1]);
// Validate and add
$result = $this->db_manager->add_keyword($keyword, $target_url);
if ($result['success']) {
$imported++;
}
}
return [
'imported' => $imported,
'duplicates' => $duplicates
];
}
User Experience: Instead of manually adding 500 keywords one by one, users can import them all in seconds.
3. Real-Time Progress Tracking
Using AJAX polling:
function checkProgress() {
$.ajax({
url: aiinlito_ajax.ajax_url,
type: 'POST',
data: {
action: 'aiinlito_get_progress',
nonce: aiinlito_ajax.progress_nonce
},
success: function(response) {
if (response.success) {
updateProgressBar(response.data.percentage);
if (response.data.pending > 0) {
// Still processing, check again in 2 seconds
setTimeout(checkProgress, 2000);
}
}
}
});
}
Result: Users see a smooth progress bar instead of a frozen screen.
4. Keyword Usage Analytics
public function get_keyword_usage_data($keyword_id) {
global $wpdb;
// Get linked pages
$linked_pages = $wpdb->get_results($wpdb->prepare(
\"SELECT DISTINCT post_id, post_type, created_date
FROM {$this->tracking_table}
WHERE keyword_id = %d
ORDER BY created_date DESC\",
$keyword_id
));
// Get post details
$pages_data = [];
foreach ($linked_pages as $page) {
$post = get_post($page->post_id);
if ($post) {
$pages_data[] = [
'title' => $post->post_title,
'url' => get_permalink($post->ID),
'linked_date' => $page->created_date
];
}
}
return $pages_data;
}
Value: Users can see exactly where each keyword is used, helping with SEO audits.
๐จ UI/UX Design Philosophy
Principles I Followed:
Progressive Disclosure: Don't overwhelm users. Show basic options first, advanced settings behind a toggle.
Immediate Feedback: Every action gets instant visual confirmation.
Mobile-First: The admin interface works perfectly on phones and tablets.
Accessibility: Proper ARIA labels, keyboard navigation, screen reader support.
The Dashboard
// Keywords Page
<div class=\"aiinlito-dashboard\">
<div class=\"stats-overview\">
<div class=\"stat-card\">
<span class=\"stat-number\"><?php echo $total_keywords; ?></span>
<span class=\"stat-label\">Active Keywords</span>
</div>
<div class=\"stat-card\">
<span class=\"stat-number\"><?php echo $usage_count; ?></span>
<span class=\"stat-label\">Links Created</span>
</div>
</div>
<div class=\"keywords-table\">
<!-- Interactive table with sort, search, pagination -->
</div>
</div>
Design Goals:
- Clean, modern interface
- Color-coded status indicators
- Smooth animations
- Responsive grid layout
๐งช Testing & Quality Assurance
Testing Strategy
-
Manual Testing:
- Tested on WordPress 5.0 through 6.8.2
- Multiple PHP versions (7.4, 8.0, 8.1, 8.2)
- Various hosting environments (shared, VPS, dedicated)
- Different themes and page builders
-
Performance Testing:
- Tested with databases containing 10,000+ posts
- Monitored memory usage and query times
- Optimized slow queries with indexes
-
Security Audits:
- Ran through WordPress Plugin Checker
- Used PHP CodeSniffer with WordPress standards
- Manual code review for vulnerabilities
-
Compatibility Testing:
- Page builders: Elementor, Gutenberg, Classic Editor
- SEO plugins: Yoast, RankMath, All in One SEO
- Caching plugins: WP Super Cache, W3 Total Cache
- Multilingual plugins: WPML, Polylang
๐ Performance Optimization Techniques
1. Query Optimization
Before:
// Slow: N+1 query problem
foreach ($keywords as $keyword) {
$usage = $wpdb->get_var(\"SELECT COUNT(*) FROM tracking WHERE keyword_id = {$keyword->id}\");
}
After:
// Fast: Single query with JOIN
$keywords_with_usage = $wpdb->get_results(
\"SELECT k.*, COUNT(t.id) as usage_count
FROM keywords k
LEFT JOIN tracking t ON k.id = t.keyword_id
GROUP BY k.id\"
);
2. Memory Management
// Process large datasets in chunks
$offset = 0;
$limit = 100;
while ($posts = get_posts([
'posts_per_page' => $limit,
'offset' => $offset,
'post_status' => 'publish'
])) {
foreach ($posts as $post) {
process_post($post);
}
$offset += $limit;
// Free memory
wp_cache_flush();
}
3. Database Indexing
-- Speed up keyword searches
CREATE INDEX idx_keyword_status ON keywords(status);
CREATE INDEX idx_keyword_usage ON keywords(usage_count);
-- Speed up tracking queries
CREATE INDEX idx_tracking_keyword ON link_tracking(keyword_id);
CREATE INDEX idx_tracking_post ON link_tracking(post_id);
-- Speed up queue processing
CREATE INDEX idx_queue_status ON processing_queue(status, priority, created_date);
๐ง Challenges & Solutions
Challenge 1: WordPress Block Editor (Gutenberg)
Problem: Gutenberg uses HTML comments for blocks:
<!-- wp:paragraph -->
<p>This is content</p>
<!-- /wp:paragraph -->
If we're not careful, links could be inserted inside these comments, breaking the editor.
Solution:
// Remove block comments before searching
$search_content = preg_replace('/<!--\s*wp:.*?-->/s', '', $content);
$search_content = preg_replace('/<!--\s*\/wp:.*?-->/s', '', $search_content);
// Find matches in clean content
$matches = $this->find_keyword_matches($search_content, $keyword);
// Map positions back to original content
$original_position = $this->find_original_position($content, $matched_text);
Challenge 2: Case-Preserving Links
Problem: Users want to add \"WordPress SEO\" as a keyword, but their content might have \"wordpress seo\", \"WORDPRESS SEO\", or \"WordPress seo\". We need to link all variations but preserve their original case.
Solution:
// Case-insensitive search
$pattern = '/\b' . preg_quote($keyword, '/') . '\b/i';
if (preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE)) {
// $match[0][0] contains the actual text with original case
$matched_text = $match[0][0]; // \"wordpress seo\"
$position = $match[0][1];
// Create link preserving the case
$link = '<a href=\"' . $url . '\">' . $matched_text . '</a>';
}
Challenge 3: Link Removal on Keyword Deletion
Problem: When a keyword is deleted, we need to remove all its links from potentially thousands of posts without breaking the content.
Solution:
private function remove_keyword_links($keyword_id) {
// Get all posts with this keyword
$tracking_records = $wpdb->get_results(
\"SELECT * FROM {$this->tracking_table} WHERE keyword_id = {$keyword_id}\"
);
foreach ($tracking_records as $record) {
$post = get_post($record->post_id);
$content = $post->post_content;
// Remove link but keep the text
$pattern = '/<a[^>]*data-aiinlito-keyword-id=\"' . $keyword_id . '\"[^>]*>(.*?)<\/a>/i';
$cleaned_content = preg_replace($pattern, '$1', $content);
// Update post
wp_update_post([
'ID' => $post->ID,
'post_content' => $cleaned_content
]);
}
}
Challenge 4: Shared Hosting Limitations
Problem: Many users are on cheap shared hosting with:
- Limited PHP memory (64-128MB)
- Strict execution time limits (30 seconds)
- Restricted database resources
Solution:
- Background processing with small batches
- Configurable batch sizes (default: 10 items)
- Automatic timeout detection and recovery
- Memory-efficient algorithms
๐ Lessons Learned
1. Start with Performance in Mind
Don't wait until you have performance issues to optimize. Build efficient systems from day one:
- Use proper database indexes
- Implement caching early
- Batch operations by default
- Monitor memory usage
2. Security Can't Be an Afterthought
Every AJAX endpoint, every database query, every file upload needs security checks. It's tedious, but one vulnerability can destroy trust.
3. User Experience > Feature Count
I initially wanted to add 50+ features. Instead, I focused on 10 core features done exceptionally well with a beautiful UI. Result? Higher user satisfaction.
4. Testing on Real Sites is Crucial
Local development can't replicate:
- Slow shared hosting
- Conflicting plugins
- Unusual theme configurations
- Large databases (10,000+ posts)
Always test on real, production-like environments.
5. Documentation Matters
I spent almost as much time on documentation as coding:
- Detailed README
- Video tutorials
- FAQ section
- Code comments
Result? Far fewer support requests.
6. WordPress Standards Exist for a Reason
Following WordPress Coding Standards:
- Makes code more maintainable
- Ensures compatibility with other plugins
- Provides security best practices
- Helps with WordPress.org approval
๐ฎ Future Enhancements
Here's what's on the roadmap:
AI Keyword Suggestions: Analyze content and suggest relevant internal linking opportunities
Link Decay Detection: Alert when target URLs return 404s
A/B Testing: Test different anchor texts for the same target URL
Anchor Text Variations: Instead of always using the same keyword, use synonyms
Content Cluster Visualization: Visual map showing how content is interconnected
REST API: Allow external tools to manage keywords programmatically
Link Priority System: Prioritize certain keywords over others
Seasonal Linking: Automatically adjust links based on time of year
๐ก Key Takeaways for WordPress Developers
If you're building a WordPress plugin, here's my advice:
1. Architecture First
โ
Separate concerns (database, logic, UI)
โ
Use dependency injection
โ
Follow SOLID principles
โ
Make code testable
2. Performance Matters
โ
Use background processing for heavy tasks
โ
Implement proper caching
โ
Optimize database queries
โ
Test with large datasets
3. Security is Non-Negotiable
โ
Nonce verification on all AJAX calls
โ
Capability checks everywhere
โ
Sanitize inputs, escape outputs
โ
Use prepared statements
4. User Experience Wins
โ
Real-time feedback
โ
Clear error messages
โ
Intuitive navigation
โ
Mobile responsiveness
5. Code Quality Matters
โ
Follow WordPress Coding Standards
โ
Write meaningful comments
โ
Use consistent naming conventions
โ
Keep functions small and focused
๐ฌ Conclusion
Building AI Internal Linking Tool was a journey that taught me invaluable lessons about:
- WordPress plugin architecture
- Performance optimization at scale
- Security best practices
- User experience design
- Code maintainability
The plugin now successfully automates internal linking for thousands of websites, from small blogs to enterprise sites with millions of pages.
Most importantly: It solves a real problem in a way that respects user's servers, follows WordPress standards, and provides genuine value.
๐ Resources & References
WordPress Development:
Performance Optimization:
Security:
๐ Connect & Learn More
- Plugin Website: kumarharshit.in/ai-internal-linking-tool
- WordPress.org: AI Internal Linking Tool
- Portfolio: kumarharshit.in
Have questions about WordPress plugin development? Drop them in the comments below! ๐
I'm happy to discuss architecture decisions, performance optimization, or any other aspect of the development process.
About the Author ๐จ
Kumar Harshit - AI SEO Specialist & Tool Developer
Iโm Kumar Harshit, an AI-driven SEO Specialist with over 7 years of hands-on experience in building WordPress solutions that blend speed, scalability, and intelligence. My core mission is to create smart, automation-ready SEO tools that empower websites to rank faster and perform better.
๐ฏ My Expertise
- WordPress Development - Custom plugins and performance optimization
- SEO Optimization - Technical SEO and search engine compliance
- AI Integration - Implementing AI-powered solutions for web
- Performance Engineering - Scalable, high-traffic solutions
๐ ๏ธ Tools I've Created
- Free Online News Sitemap Generator - Zero-config Google News sitemaps
- News Sitemap Generator Plugin - Zero-config Google News sitemaps ### ๐ Connect With Me
- Website: kumarharshit.in
- LinkedIn: linkedin.com/company/kumarharshit-in
- GitHub: github.com/Harshit-Kumar
"I believe the future of SEO is automation โ where smart tools do the heavy lifting, and humans focus on creativity and strategy."
Wrap Up ๐ฏ
Enjoyed this post? Drop a โค๏ธ and share your take in the comments!
Letโs talk about AI-driven SEO, WordPress performance, or how to build smarter tools for the open web.
Tags: #WordPress
#SEO
#Performance
#PluginDevelopment
#GoogleNews
#WebDev
#PHP
#Optimization
Top comments (0)