Skip to content

Instantly share code, notes, and snippets.

@ivanmercedes
Created February 25, 2026 22:32
Show Gist options
  • Select an option

  • Save ivanmercedes/4599be70b83efe17f933b6a6d85ef5b4 to your computer and use it in GitHub Desktop.

Select an option

Save ivanmercedes/4599be70b83efe17f933b6a6d85ef5b4 to your computer and use it in GitHub Desktop.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Symfony\Component\DomCrawler\Crawler;
use Illuminate\Support\Facades\Http;
class SeoCheckerController extends Controller
{
public function index(Request $request)
{
return Inertia::render('seo');
}
/**
* Analiza una URL y devuelve un puntaje SEO
*
* @param Request $request
*/
public function analyzeUrl(Request $request)
{
$validated = $request->validate([
'url' => ['required', 'url'],
]);
$baseUrl = parse_url($validated['url'], PHP_URL_SCHEME) . '://' . parse_url($validated['url'], PHP_URL_HOST);
$visited = [];
$results = [];
$this->crawl($validated['url'], $baseUrl, $visited, $results);
$returnData = $this->calculateSeoScore($results);
return Inertia::render('seo', [
'item' => $returnData
]);
}
/**
* Realiza un rastreo de la página web
*
* @param string $url URL de la página a rastrear
* @param string $baseUrl URL base de la página
* @param array $visited URLs visitadas
* @param array $results Resultados del rastreo
* @param int $depth Profundidad actual
* @param int $maxDepth Profundidad máxima
* @return void
*/
private function crawl($url, $baseUrl, &$visited, &$results, $depth = 0, $maxDepth = 10): void
{
$normalizedUrl = $this->normalizeUrl($url);
if (isset($visited[$normalizedUrl]) || $depth > $maxDepth) {
return;
}
$visited[$normalizedUrl] = true;
$response = Http::get($url);
if (!$response->successful()) {
return;
}
$crawler = new Crawler($response->body(), $url);
$has_title = $crawler->filter('title')->count() ? true : false;
$has_description = $crawler->filter('meta[name="description"]')->count()
? true
: false;
$title = $crawler->filter('title')->count() ? $crawler->filter('title')->text() : '';
$metaDescription = $crawler->filter('meta[name="description"]')->count()
? $crawler->filter('meta[name="description"]')->attr('content')
: '';
$h1Text = $crawler->filter('h1')->count() ? $crawler->filter('h1')->text() : 'No H1 found';
$has_h1 = $crawler->filter('h1')->count() ? true : false;
$results[] = [
'url' => $normalizedUrl,
'title' => $title,
'meta_description' => $metaDescription,
'has_description' => $has_description,
'has_title' => $has_title,
'h1' => $h1Text,
'has_h1' => $has_h1
];
$links = $crawler->filter('a')->links();
foreach ($links as $link) {
$href = $link->getUri();
if (
strpos($href, $baseUrl) === 0 &&
strpos($href, '#') === false &&
!preg_match('/\.(pdf|docx?|xlsx?|pptx?|zip|rar|png|jpg|jpeg|webp)$/i', $href)
) {
$this->crawl($href, $baseUrl, $visited, $results, $depth + 1, $maxDepth);
}
}
}
/**
* Normaliza una URL para evitar duplicados
*
* @param string $url URL a normalizar
* @return string URL normalizada
*
*/
private function normalizeUrl($url): string
{
$parsedUrl = parse_url($url);
$scheme = $parsedUrl['scheme'] ?? 'http';
$host = $parsedUrl['host'] ?? '';
$path = $parsedUrl['path'] ?? '/';
$path = ($path === '/') ? '/' : rtrim($path, '/');
$normalizedUrl = "{$scheme}://{$host}{$path}";
if (isset($parsedUrl['query'])) {
$normalizedUrl .= "?{$parsedUrl['query']}";
}
return $normalizedUrl;
}
/**
* Calcula el puntaje SEO de las páginas
*
* @param array $data Datos de las páginas
* @return array Datos de las páginas con puntaje SEO
*
*/
private function calculateSeoScore(array $data): array
{
$totalScore = 0;
$pageCount = count($data);
$data = array_map(function ($page) use (&$totalScore) {
$score = 100;
if (!$page['has_title']) {
$score -= 40;
}
if (!$page['has_description']) {
$score -= 20;
}
if (!$page['has_h1']) {
$score -= 30;
}
$score = max(0, $score);
$totalScore += $score;
return array_merge($page, ['score' => $score]);
}, $data);
$averageScore = $pageCount > 0 ? $totalScore / $pageCount : 0;
return ['pages' => $data, 'average_score' => $averageScore];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment