SEO in Laravel - Optimizing JavaScript files in a blog

- Andrés Cruz - ES En español

Video thumbnail

Content Index

When I first entered the world of "SEO in Laravel", I thought it was more about content and less about code. Wrong. Technical SEO can raise or sink a project, especially when working with frameworks like Laravel, where performance, HTML structure, and the way you load resources can completely change your position in the SERPs. I'm not exaggerating: small technical details can give you a massive push… or take away 40 PageSpeed points overnight.

In my case, I started with a PageSpeed score of 37 on some blog posts. Yes, 37. I was almost embarrassed. And mind you: it wasn't because Laravel was slow. The problem was mine: unnecessary scripts, killer iframes, poorly optimized images, and JavaScript that looked like it came out of a forgotten warehouse.

Throughout this article I am going to tell you everything I learned optimizing my site, how I reached 100 in performance, what things can still be improved, and how to prevent your Laravel project from ending up drowned by YouTube, Facebook, GTM, or any other script you decide to add "just in case".

SEO, short for Search Engine Optimization, is the set of techniques we apply on our web pages to help them position organically —without paying— in the top results of search engines like Google or Bing. Aspects such as content including relevant keywords, a correct heading hierarchy (h1, h2, h3…), and a good HTML organization are the foundations of SEO. And Laravel, being the PHP framework par excellence, has a lot to say about how we apply these practices.

If you have a blog or an online store built with Laravel, having the right tools to generate metadata is fundamental for Google and other search engines to index you correctly.

I am not going to give you recycled theory: here there is only practice, sweat, testing, errors, and real results.

SEO Fundamentals in Laravel: The Essentials That Actually Impact

Before diving into technical hacks and advanced performance tricks, SEO in Laravel has three fundamental pillars that you must master: clean URLs, well-generated meta tags, and a coherent HTML structure. Without these three solid pillars, any additional optimization will be building on sand.

Clean URLs, Routing, and Semantic Structure

Laravel gives us a fantastic foundation with its routing system. Creating clean, predictable, short URLs with keywords is as simple as:

Route::get('/blog/{slug}', [PostController::class, 'show']);

This allows for a clear and direct URL structure, essential for indexing. But the key is not only creating SEO-friendly routes, but also maintaining a correct heading hierarchy in Blade views.

I confess that, for a while, I had an h1, then an h3… and suddenly went back to an h2. PageSpeed marked it as an accessibility error, and it also affects SEO directly. Since I corrected the heading hierarchy, the semantics of my pages improved considerably.

Dynamic Meta Tags with Blade and SEO Helpers

Laravel and Blade make easy what many CMSs unnecessarily complicate: dynamic meta tags. If your titles, descriptions, and OpenGraph tags are generated from the actual content of each page, Google rewards you because it better understands the intent behind each URL.

I work like this:

<title>{{ $post->title }}</title>
<meta name="description" content="{{ $post->description }}">
<link rel="canonical" href="{{ url()->current() }}">

Simple, clean, and functional.

Open Graph, Twitter Cards, and Canonicals Without Breaking Your Architecture

Most competitors forget that social engines also influence indirect SEO. Having well-configured og: tags increases CTR from social networks, and that ends up boosting your domain authority. It's not magic; it's semantic coherence and a good practice that costs very little to implement in Laravel.

Extreme Performance Optimization in Laravel (The Real SEO Factor)

This is where Laravel shines… or where you can throw it off a cliff if you don't take care of what you load. Google is clear: performance = SEO. PageSpeed Insights evaluates JavaScript, CSS, images, visual stability (CLS), accessibility, and HTML efficiency down to the millimeter.

How to Analyze Which JavaScript Blocks Your Page

I always start by opening DevTools → Network tab → .js filter.
When I did this with my blog for the first time, I found scripts I didn't even know were loading: GTM, Google Ads, Facebook Pixel… all running before the main content. For a website where these scripts are not critical on the first render, that is literally a crime against performance.

I delayed them all using the defer attribute or loading them on demand. That single change noticeably improved the Time to Interactive (TTI).

JavaScript Modularization: From a Giant blog.js to Lightweight Scripts

I had a huge blog.js where I put everything I needed "at some point." Rookie mistake. Over time I split it into modules, eliminated unnecessary dependencies, and left only the truly required functions. Today it weighs less than 300 lines and executes on demand.

For example, I completely removed axios; with native fetch() I solved all my HTTP request needs. This decision not only saves bundle size, but also eliminates external dependencies that could become outdated.

Removing What's Extra: Dashboard Scripts, Plugins, and Ghost Resources

Something PageSpeed pointed out to me was that admin panel scripts were loading on the public frontend. It makes no sense, but it happens more than you think, especially with poorly inherited packages or configurations from previous versions of the project.

I removed them all. Result: faster pages and a cleaner DOM for Google to process without friction.

Lazy Loading of JS and Critical Resources

My rule is simple and has no exceptions:

  • Critical CSS: immediate load.
  • Custom JavaScript: on demand or with defer.
  • Third-party scripts (GTM, Pixel, Ads): as late as possible.

With that alone, you already gain important points in PageSpeed.

The YouTube Case: How a Single iframe Can Destroy Your SEO

Video thumbnail

Here comes the big drama. YouTube's native <iframe> is poison, plain and simple. If you embed it just as YouTube gives it to you, you add more than 500 KB of unnecessary JavaScript that runs before the user even clicks on the video.

To analyze it, open the developer tools, go to the Network tab, and filter by .js. Upon reloading the page, you will clearly see all the scripts loading. YouTube's script is a critical part of the problem: it brings too many resources with it, slowing down the full page load even when the user never plays the video.

How I Went from 37 to 100 on PageSpeed: The <iframe> Problem

In this and in upcoming videos, I am going to explain how I went from a performance score of 37 on my posts to reaching 100. Which, honestly, still surprises me, because I still have things to polish. Even if the metric is perfect, the site isn't completely. But the improvement is real and replicable, and that's what matters most.

In this first video, I focus exclusively on the blessed YouTube <iframe>, which was the main party responsible for that embarrassing score of 37.

The Dilemma of the Embedded Video on the Blog

If you have a blog, you've probably been told that you should combine text and video content, especially if you create mixed content: you make a video and then write the article, or vice versa. Ideally, both should coexist in the same post: the video at the top and the article at the bottom. That way, you boost your content by offering two different formats that complement each other.

So far, so good.
The problem arrives when you decide to embed the video directly with YouTube's standard <iframe>, which is a true performance nightmare.

That was the cancer I had on my blog, and that's why I want to explain exactly what was happening and how I solved it.

Why YouTube's Native iframe Destroys PageSpeed

When I analyzed my posts with DevTools, I confirmed that the <iframe> loaded a battery of YouTube scripts —including its tracking and playback APIs— even if the user never watched the video. That killed my LCP (Largest Contentful Paint), the metric with the most weight in Google's ranking currently.

YouTube's <iframe> was the main responsible party for that famous "37" I started with.

Solution: WebP Thumbnail + On-Demand Player

What I do now is super simple and elegant:

  • I load a local, lightweight .webp image as the video thumbnail.
  • When the user clicks on the thumbnail, only then do I load YouTube's real <iframe>.
  • The video appears as if it had always been there.

Result:

  • Ultra-fast first render load.
  • Zero blocking YouTube JavaScript.
  • Clean and natural user experience.

The most ironic part is that YouTube gives you a .jpg thumbnail by default… but Google penalizes you if you don't use .webp. Excellent consistency from Google with Google.

How to Recover Rich Snippets with JSON-LD (VideoObject) — Structured Data

If you remove the native <iframe>, Google no longer automatically detects your embedded video. The solution: explicitly declare it with JSON-LD using the VideoObject schema.

I generate this block dynamically in Blade:

{
 "@context": "https://schema.org",
 "@type": "VideoObject",
 "name": "{{ $post->title }}",
 "description": "{{ $post->description }}",
 "thumbnailUrl": "my-thumbnail.webp",
 "uploadDate": "{{ $post->date }}",
 "embedUrl": "https://www.youtube.com/embed/{{ $videoId }}",
 "publisher": {
   "@type": "Organization",
   "name": "Desarrollo Libre"
 }
}

In the source code, the data I declare are:

  • Video title
  • Description (the same as the post's)
  • Publication date
  • Author or channel name (in my case, "Desarrollo Libre")
  • Channel logo
  • Video URL on YouTube

Since I added this structured data block, Google understood and started displaying my videos again in rich results without penalizing page performance.

What About Plugins Like lite-youtube-embed?

There is the lite-youtube-embed package, which does something similar to my solution but through a web component. The problem is that you marry its specific syntax:

<lite-youtube videoid="YourVideoID"></lite-youtube>

And that is just what I didn't want. I prefer to maintain the content using the standard <iframe> in the data source, to process it internally and transform it. That way the content remains agnostic to any library. But if you prefer to go the plugin route, it's also a valid and fast option.

Identification and Audit of My JavaScript Files

At this point, we are clear on which scripts are not part of the core of the site —like the Facebook Pixel or Google Ads— and we also know necessary external resources —like YouTube's— that do add value to the user even if they penalize performance when not managed well.

The next step is to audit our own .js files. In my case, they are mainly two:

  • highlight.js
  • blog.js

Image, Multimedia, and DOM Optimization

Both files are authored by me, and that is already a huge advantage because before I had everything in a single monolithic file: the famous blog.js. It included absolutely everything. Following performance recommendations, I decided to split it into smaller, more specialized modules.

I am also eliminating dependencies I no longer need. For instance, I have migrated all HTTP requests from axios to native fetch(). There might be some leftover trace out there, but little by little I am organizing everything.

Replacing Axios and Other Scripts

I have already completely removed axios from the blog bundle. I also considered removing Alpine.js, but it weighs so little (~8KB minified) that for now I am keeping it; the cost/benefit ratio doesn't justify removing it.

Another script I am evaluating is highlight.js. This one is fundamental for me, since it applies syntax highlighting to code in articles. Basically it detects <pre> and <code> blocks, tokenizes them, and applies visual styles so that the code is readable and attractive.

Code Organization and Conditional Loading

What I am doing is organizing resource loading intelligently. For example, with highlight.js I have separated responsibilities: its CSS goes in the main layout to avoid flashes of unstyled content, and I load the JS only when the page actually needs it, under a specific condition. I will explain that logic to you in detail in a future video.

The blog.js file now only contains minimal functions: local searches and requests. It has fewer than 300 lines and I plan to optimize it even further. It only keeps the essential.

Great Trick for SEO in Laravel: Lazy Loading and Pre-rendering Content

Video thumbnail

I explained to you how I went from a PageSpeed score of 50 to 73 simply by swapping the blessed YouTube <iframe> for the on-demand WebP thumbnail. That change alone was already a massive jump. Now I will continue the explanation from that point and show you how I reached 100 with the rest of the changes I made.

Real Example: The Road from 37 to 100 on PageSpeed

I'll also take the opportunity to mention that this material will be part of a course I plan to create later. Currently I am working on my online store —as of the recording date—, but later I will come with a full course on how to create a well-optimized blog from scratch, explaining all these details and much more.

This is the point where everything fits together. The path went more or less like this:

  1. Removed axios
  2. Split my JS into specialized modules
  3. Removed dashboard scripts leaking into the frontend
  4. Implemented .webp thumbnails for YouTube with on-demand loading
  5. Added JSON-LD with the VideoObject schema to recover lost SEO
  6. Improved accessibility (alt, aria-label attributes, etc.)
  7. Reorganized heading hierarchy (h1 → h2 → h3)
  8. Integrated deferred loading with defer for Alpine.js, highlight.js, and other non-critical scripts

Technical SEO in Laravel: Essential Tasks That Google Does Evaluate

Improving Accessibility: alt, aria-label Attributes, and Derivatives

I also made adjustments to images, ensuring I included the alt attribute with a proper description on each one. And something very important: respecting the heading hierarchy. If you place an h1, the next one must be an h2, then h3, and so on. Jumping from h1 directly to h3 breaks the semantic logic of the content, and PageSpeed —and Google— let you know.

Laravel makes it easy to apply real technical SEO if you know where to touch. The framework doesn't limit you; on the contrary, it gives you the full control you need.

XML Sitemap with Spatie

The spatie/laravel-sitemap package is practical, simple, and fully automatic. It generates the sitemap.xml by traversing your routes or models, and updates it without you having to intervene. Essential for serious projects.

robots.txt, Canonicals, and Indexability

Laravel doesn't touch this by default, but creating a dynamic robots.txt is simple with a basic route. Canonical URLs should be generated automatically based on the active route to avoid duplicate content penalized by Google.

Security and HTTPS

HTTPS is mandatory nowadays; Google uses it as a ranking signal. Laravel facilitates CSRF management, secure headers, and security best practices natively.

Accessibility and Semantics

From here on, the rest of the changes focused on making loading more efficient and accessible. For example, I added aria-label attributes to buttons to indicate their function to screen readers. Tools used by people with visual impairments —and also evaluated by Google— that need to know what each interactive element does:

Buttons do not have accessible names.
Below are tips to improve the semantics of your application's controls. These tips can improve the experience for users of assistive technologies, such as screen readers.

<button aria-label="Close window">
   <svg><!-- close icon --></svg>
</button>

Screen readers appreciate these types of attributes, and Google also takes them into account in its accessibility metrics.

Recommended SEO Packages for Laravel Projects

  • spatie/laravel-sitemap → automatic sitemap.xml
  • spatie/schema-org → clean and typed JSON-LD
  • artesaos/seotools → complete meta tags (Open Graph, Twitter Cards, JSON-LD)
  • ralphjsmit/laravel-seo → modern alternative with Filament support

All these packages help and save time, but none replaces the task of optimizing your JavaScript, images, and HTML structure. They are tools, not magic solutions.

Optimizing Render-Blocking Resources

In addition to the above, I optimized the overall server response. Although swapping the <iframe> helped tremendously, I also started removing all unnecessary JavaScript. There were resources loading from the admin dashboard on the public frontend, making no sense at all. I removed all of that mercilessly.

JavaScript Reduction and Cleaner Code

The JavaScript that was loading affected performance significantly. The YouTube <iframe> alone brought a ridiculous amount of resources. And all these small changes directly affect how the browser renders the page and how Google evaluates it.

As you solve those problems, loading improves, the server responds faster, and everything is processed more efficiently. That's why, with strategic and well-prioritized changes, the website evolved dramatically.

The Excessive DOM Problem

One of the aspects Google also pointed out to me was the excessive size of the DOM. This is because in my content I have two main types: posts with the YouTube video and its transcript, and posts showing complete courses.

When I show a course, the document is long because it contains all sections with their descriptions. I place all that structure inside the HTML so Google understands what each part does. That's why the DOM is so extensive.

This structure comes directly from my course platform, where each section has its lessons. I simply reused that structure in the blog posts. So, even if it is a long DOM, the content is useful and semantically correct.

Formatting and Visual Accessibility Issues

There are a few visual details I also need to improve. For example, some color contrasts are too strong. I also have pending reorganizing the post listing so that the most recent ones appear first.

And something I've always found curious: when I use YouTube thumbnails, they are served as .jpg, not as .webp, which is Google's preferred format. The ironic thing is that if you upload an image directly to YouTube and right-click to download it, many times you do get a .webp. It seems like they do it on purpose to complicate our lives.

JavaScript Reduction: In Depth
Going back to key aspects: reducing JavaScript was essential. Also taking care of descriptive attributes in HTML. For example, images must have their alt attribute, and buttons must have their aria-label to indicate what action they perform. This seems like a minor detail, but Google penalizes it directly in the accessibility score.

What matters most to me is that the whole process remains as natural as possible. If a post has a YouTube video, in the CMS I simply put the standard <iframe> and my code does the rest of the optimization work on the backend.

Source Code and Highlight.js

This blog is targeted at developers, so obviously there are code blocks in many articles. I use highlight.js to apply syntax highlighting and display the code clearly and attractively.

The problem arises when you have a technical book with 300 pages full of code: highlight.js starts to get heavy and in some cases stops working properly or slows down heavily, impacting page load.

That's why I made a key decision: process syntax highlighting only once on the server and save the result in a field called final_content. That means highlight.js runs only once (when saving the content), and the frontend simply serves the already processed HTML. That way I save the entire plugin load on every visit.

Alpine.js and Minimal Scripts

Another part I optimized was the script loading strategy. I use Alpine.js for the hamburger menu and share buttons. Although it weighs very little (~8KB minified), I decided to also delay its loading with defer so as not to block initial rendering.

The key question I always ask myself is: what is the absolute minimum my site needs to load to render correctly? CSS, without a doubt. Also highlight.js's CSS for code highlighting. Google penalizes abrupt style shifts (CLS), which is why that CSS must be loaded from the start, in the <head>, even if you load the JS later.

Common Errors That Ruin SEO in Laravel

  • Loading GTM, Ads, or Facebook Pixel too early, blocking rendering.
  • Jumping from an h1 to an h3 without going through an h2.
  • Embedding YouTube videos with the unoptimized native <iframe>.
  • Leaving repeated or unnecessary scripts that are never actually used.
  • Not managing alt attributes on images nor aria-label on buttons.
  • Serving heavy images or images in .jpg/.png format instead of .webp.

Laravel SEO: Packages to Generate Meta Tags

Video thumbnail

There are multiple packages to generate SEO tags in Laravel, which is fantastic because there are parts of the application where we need to inject dynamic meta tags without worrying about whether the syntax is correct: the package takes care of that for us.

SEOTools

Generate metatags in Laravel, ideal for SEO
Video thumbnail

Metatags are primary elements for providing information to search engines, particularly indexing bots like Googlebot. Elements such as the <title> (which technically isn't a metatag but works like one), the meta description, the OG image, among others, are fundamental to making a first impression of your site so search engines know how to present it in search results.

There are multiple types of metadata we can use. In practice, they are HTML tags that serve to give a title, a description, and a reference to a main image for the post. We can also customize tags for social networks like Facebook or Twitter using the og: and twitter: prefixes. Keywords (keywords) no longer carry real weight in Google, but the three essential pieces of data that do matter are:

  1. Title
  2. Description
  3. Image (OG Image)

artesaos/seotools is a package that allows you to generate all these meta tags easily through method chaining. Official repository:

https://github.com/artesaos/seotools

$ composer require artesaos/seotools

Add the Service Provider

In Laravel 10 or lower, open the config/app.php file and add the provider. In Laravel 11+, automatic package discovery usually takes care of this:

'providers' => [
   Artesaos\SEOTools\Providers\SEOToolsServiceProvider::class,
],

Configure the package

Now publish the configuration file. Most likely, you will want to adjust some default values:

php artisan vendor:publish --provider="Artesaos\SEOTools\Providers\SEOToolsServiceProvider"

This will create the config/seotools.php file, where you can define default values for title, description, Open Graph, and Twitter Cards across the entire site. From your controller, you could use it like this:

SEOTools::setTitle("Latest posts");
SEOTools::opengraph()->addProperty('type', 'articles');
SEOTools::twitter()->setSite('@LibreDesarrollo');
SEOTools::jsonLd()->addImage(URL::to('/public/images/logo/logo.png'));
SEOTools::setDescription("Here you will find the latest posts that I have uploaded to my blog.");

From the Blade view:

{!! SEO::generate() !!}
<!-- MINIFIED -->
{!! SEO::generate(true) !!}

And the output in HTML would look something like this:

<title>Latest posts</title>
<meta name="description" content="Here you will find the latest posts that I have uploaded to my blog.">
<meta property="og:title" content="Latest posts"><meta property="og:type" content="articles">
<meta property="og:description" content="Here you will find the latest posts that I have uploaded to my blog.">
<meta name="twitter:title" content="Latest posts"><meta name="twitter:site" content="@LibreDesarrollo">
<meta name="twitter:description" content="Here you will find the latest posts that I have uploaded to my blog.">
<script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","name":"Últimas publicaciones","description":"Here you will find the latest posts that I have uploaded to my blog."}</script>

To customize the package with default values or a title suffix, publish the configuration file (if you haven't done so already):

$ php artisan vendor:publish --provider="Artesaos\SEOTools\Providers\SEOToolsServiceProvider"

A more complete example using all available facades:

SEOMeta::setTitle("Últimas publicaciones");
SEOMeta::setDescription("Aquí encontrarás las últimas publicaciones que he subido a mi blog.");
SEOTools::setDescription("Aquí encontrarás las últimas publicaciones que he subido a mi blog.");
OpenGraph::setDescription("Aquí encontrarás las últimas publicaciones que he subido a mi blog.");
OpenGraph::setTitle("Últimas publicaciones");
OpenGraph::addProperty('type', 'articles');
TwitterCard::setSite('@acy291190');
TwitterCard::setTitle("Últimas publicaciones");

I use those statically for a specific section that displays the latest posts. But for individual posts, the data comes from the model:

SEOMeta::setTitle($post->title);
SEOMeta::setDescription($post->description);
SEOTools::setDescription($post->description);
OpenGraph::setDescription($post->description);
OpenGraph::setTitle($post->title);
OpenGraph::addProperty('type', 'articles');
TwitterCard::setTitle($post->title);
TwitterCard::setSite('@acy291190');

In the master Blade file (layout)

In your main Laravel template, define the location where the meta tags will be injected, usually inside the <head>:

<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1">
...
   @yield('headseo')
</head>

And that's it. With this, you can manage SEO metadata in a centralized and dynamic way across your entire Laravel application.

Extra: images in meta tags

Most likely, you will want to include image references in your OG and Twitter meta tags. To do that:

OpenGraph::addImage(URL::to('/public/images/logo/logo.png'));
TwitterCard::setImage(URL::to('/public/images/logo/logo.png'));

Laravel SEO (ralphjsmit/laravel-seo)

This package generates meta tags oriented toward Eloquent models. Features provided include:

  • <title> tag with a configurable site-wide suffix
  • Meta tags (author, description, image, robots, etc.)
  • OpenGraph tags (Facebook, LinkedIn, etc.)
  • Twitter Cards tags
  • Structured data (article and breadcrumb navigation)
  • Favicon
  • robots tag

https://github.com/ralphjsmit/laravel-seo

$ composer require ralphjsmit/laravel-seo

To populate the SEO data of an Eloquent model:

$post = Post::find(1);

$post->seo->update([
   'title' => 'My great post',
   'description' => 'This great post will enhance your live.',
]);

From the Blade view:

{!! seo()->for($post) !!}

There are several other configurations available in the package's official documentation.

Three powerful SEO packages for Laravel: a quick comparison

Three packages worth knowing:

  • Artesaos SEO Tools (artesaos/seotools) — Read full article
  • Ralph J. Smith Laravel SEO (ralphjsmit/laravel-seo)
  • Laravel SEO Scanner

Then, in the practical examples, we will run the scanner on two demos based on the first two packages.

1. Artesaos SEO Tools

The first package, artesaos/seotools, works like this: you have a demo page, and if you inspect the source code of a post, you will see tags like meta description, meta twitter, <title>, among others, perfectly generated.

To populate this data from the database, I built an admin panel using Filament within the same project. For instance, I can fill in the title, description, and author for a post, save the changes, and they are reflected in the database table created specifically for SEO.

When refreshing the page, you will see the titles and meta tags in the source code, including og:title and og:description. Additionally, default values can be configured through the configuration file.

This package is older (with commits dating back nine years), but it is still compatible with Laravel 12. The latest update on March 14 adapted the package for this framework version.

2. Ralph J. Smith Laravel SEO

The second package is ralphjsmit/laravel-seo. Ralph is very active in the Filament community, which is why part of this package includes native integrations for that panel.

In its main documentation, it states that in Blade you only need to call seo()->for($post) to render all meta tags. For the model to access SEO data, simply add the HasSEO trait to it, which automatically creates the relationship in the database—unlike the first package, where you manage the relationship manually.

You can change the title, description, etc., directly from the panel, and the changes reflect immediately on the page.

Both packages allow you to work with meta tags, titles, and SEO tags in HTML, which remains entirely relevant for search engine rankings.

3. Laravel SEO Scanner

The third package is Laravel SEO Scanner, which allows you to scan your site's pages and review their SEO configuration according to various criteria in an automated way.

For example, after installing it in the first demo project, you can run:

php artisan seo:scan

And you get a detailed report: 19 successful checks and 6 failed ones, indicating exactly which elements are missing, such as the Open Graph image, the meta description, or the keywords.

Package summary

Of the three packages mentioned, each serves its own specific purpose:

  • artesaos/seotools and ralphjsmit/laravel-seo allow you to configure and manage metadata.
  • Laravel SEO Scanner serves to audit and detect issues on your pages.

The ideal combination is using one of the first two to generate meta tags, and the scanner to validate that everything is properly configured.

SEO Management in Laravel with SEO Suite

Video thumbnail

In the Laravel ecosystem, there are various tools dedicated to SEO, making essential tasks like generating meta tags, sitemaps, and structured data easier.

Recently, I evaluated a package named SEO Suite (nafakhari/seo-suite), which offers a comprehensive approach to addressing these needs in Laravel applications.

Key features of SEO Suite

Although there are consolidated alternatives in the ecosystem (like artesaos/seotools), this package gathers several key features in a single installation:

  • Sitemap Generation: Dynamically builds the sitemap.xml with the application's URL structure to facilitate indexing:
    • use webspider\SeoSuite\Domain\Sitemap\SitemapGenerator;
      
      $sitemap = new SitemapGenerator();
      
      foreach ($posts as $post) {
          $sitemap->addUrl(route('posts.show', $post), $post->updated_at->toDateString(), 'weekly', 0.8);
      }
      
      return response($sitemap->generateXml(), 200, ['Content-Type' => 'application/xml']);
  • JSON-LD Support: Incorporates structured data schemas in JSON-LD format, providing detailed context to search engines:
    • use webspider\SeoSuite\Domain\Schema\SchemaComposer;
      
      $composer = new SchemaComposer();
      $composer->addSchema(['@type' => 'Organization', 'name' => config('app.name')]);
      $composer->addSchema(['@type' => 'Article', 'headline' => $post->title]);
      
      $jsonLd = $composer->generateJsonLd(); // safe to drop straight into a <script> tag
  • Meta Tag Management: Centralized configuration of titles, descriptions, and Open Graph tags:
    • use webspider\SeoSuite\Core\Contracts\SeoRepositoryInterface;
      
      app(SeoRepositoryInterface::class)->updateForModel($post, [
          'meta_title'       => 'How to tune Laravel queries',
          'meta_description' => 'A practical guide to profiling and fixing slow Eloquent queries.',
          'canonical_url'    => 'https://example.com/blog/tune-laravel-queries',
      ]);
  • SEO Auditing and Scoring: Internal analysis tools to evaluate compliance with best practices directly from code:
    • use webspider\SeoSuite\Application\Analysis\AnalysisPipeline;
      use webspider\SeoSuite\Domain\Analysis\AnalysisContext;
      
      $result = app(AnalysisPipeline::class)->execute(new AnalysisContext(
          html: $post->body,
          title: $post->meta_title,
          description: $post->meta_description,
          focusKeyword: 'laravel queries',
          profile: 'blog',
      ));
      
      // ['overall_score' => 85, 'breakdown' => [...], 'recommendations' => [...]]

Installation and integration

Adding the package to the project is done via Composer:

$ composer require nafakhari/seo-suite

Unlike other libraries where tags are managed in a very fragmented manner, SEO Suite centralizes much of the configuration, standing out especially for its simplicity in generating sitemaps and structured schemas.

Evaluating these types of packages allows you to select the tool that best fits the architectural and maintainability requirements of each Laravel project.

Conclusion: keeping a Laravel project fast, clean, and well-ranked

SEO in Laravel is not just about "adding keywords". It is an ongoing technical process that requires periodically reviewing your scripts, images, HTML structure, and third-party integrations. Small changes add up, and sometimes a single poorly implemented <iframe> can bring everything down.

The key lies in:

  • Simplifying code and removing everything unnecessary.
  • Loading only the essentials on initial render.
  • Organizing your HTML semantically.
  • Constantly monitoring with PageSpeed Insights and DevTools.
  • If you do it right, you can achieve results like the jump I made: from 37 to 100. And most importantly: maintain that quality in the long run.

Frequently asked questions about SEO in Laravel

  • How to improve PageSpeed in Laravel?
    • In this guide, you have the detailed steps: remove render-blocking JavaScript, replace YouTube <iframe> tags with on-demand .webp thumbnails, and add structured data with JSON-LD.
  • Which SEO packages can I use in Laravel?
    • The most recommended ones are artesaos/seotools, ralphjsmit/laravel-seo, spatie/laravel-sitemap, and spatie/schema-org. Above, you have a detailed comparison of each.
  • How to optimize YouTube in Laravel without losing SEO?
    • Replace the native <iframe> with a .webp thumbnail that loads the player on click, and declare the video with a JSON-LD block using the VideoObject schema so search engines keep detecting it.
  • What influences SEO more: content or performance?
    • Both are important and complement each other. Without relevant content, you don't reach search intent; without performance, Google penalizes you in Core Web Vitals metrics. Combining both leads to strong ranking.
  • Is Laravel a good framework for SEO?
    • Yes, very much so. Laravel gives you full control over HTML generation, clean routes, dynamic metadata, and asset loading. The key is applying the technical optimizations described in this guide.

We talked about the importance of keeping in mind which JS (and other resources) are being used on the website you want to rank on Google.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

I agree to receive announcements of interest about this Blog.