When I first arrived in the world of "SEO in Laravel," I thought it was more about content and less about code. Wrong. Technical SEO can make or break a project, especially when you work with frameworks like Laravel, where performance, HTML structure, and how you load resources can completely change your position in the SERPs. I'm not exaggerating: small technical details can give you a brutal boost... 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 beware: 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'm going to tell you everything I learned optimizing my site, how I reached a score of 100 in performance, what things can still be improved, and how to prevent your Laravel project from drowning due to YouTube, Facebook, GTM, or any other script you decide to add "just in case."
SEO stands for Search Engine Optimization is a set of techniques that we use in our web pages that help them to position themselves naturally (without paying) in the first results of searches that are carried out in search engines such as Bing, Google or others, it is that simple Aspects such as that the content must contain keywords, H1s and others, a good organization... are the bases of SEO, and Laravel, being the quintessential PHP web framework, it is important how we can apply this type of practice.
If you have a blog, an online store and use Laravel, this package is ESSENTIAL to generate that metadata that Google and other engines need to index you.
I'm not going to give you recycled theory: here is only practice, sweat, testing, errors, and real results.
Laravel SEO Basics: The Essentials That Really Impact
Before diving into technical hacks, advanced adjustments, and performance tricks, SEO in Laravel has three basic pillars: clean URLs, well-generated meta tags, and a coherent HTML structure.
Clean URLs, Routing, and Semantic Structure
Laravel gives us a fantastic foundation with its routing system. Creating clean, predictable, short, and keyword-rich URLs is as easy as writing:
This allows for a clear structure, which is essential for indexing. But the key is not just to create SEO-friendly routes, but to maintain a correct heading structure in the Blade views.
I confess that, for a time, I had an H1, then an H3... and then I went back to an H2. PageSpeed flagged it as an accessibility error, and it also affects SEO. Since I corrected this, the semantics of my pages improved significantly.
Dynamic Meta Tags with Blade and SEO Helpers
Laravel and Blade facilitate something that many CMSs complicate: dynamic meta tags. If your titles, descriptions, and OpenGraph tags are generated based on the content, Google rewards you because it better understands the intent of each page.
Open Graph, Twitter Cards, and Canonicals without Breaking Your Architecture
Most competitors forget that social engines also indirectly influence SEO. Having well-generated OG tags increases the CTR from social networks, and that ends up boosting your domain authority. It's not magic; it's semantic coherence.
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 watch what you load. Google is clear: Performance = SEO. PageSpeed evaluates JavaScript, CSS, images, layout, visual stability, accessibility, and HTML efficiency.
How to Analyze Which JavaScript Blocks Your Page
I always start by opening DevTools → Network tab → filter .js. 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, it's a crime.
I deferred all of them. And yes, that change alone significantly improved the Time to Interactive.
JavaScript Modularization: From a Giant blog.js to Light Scripts
I had a huge blog.js where I put everything I needed "at some point." Rookie mistake. Over time, I divided it, eliminated dependencies, and left only the necessary functions. Today it weighs less than 300 lines and runs on demand.
For example, I completely eliminated Axios; with fetch(), I solved all my needs. This decision not only saves weight but also reduces unnecessary dependencies.
Eliminating What's Left Over: Dashboard Scripts, Plugins, and Phantom Resources
Something PageSpeed flagged for me was that admin panel scripts were being loaded on the frontend. This makes no sense, but it sometimes happens with poorly inherited packages or configurations.
I eliminated all of them. Result: faster pages and a cleaner DOM.
Deferred Loading of JS and Critical Resources
My rule is simple:
Critical CSS: immediate load.
JavaScript: on demand.
Third-parties: as late as possible.
With just that, you already gain significant points.
The YouTube Case: How a Single iframe Can Destroy Your SEO
▶
Here comes the big drama. The native YouTube iframe is poison, plain and simple. If you embed it exactly as YouTube gives it to you, you add 500 KB of unnecessary JavaScript that runs BEFORE the user even clicks.
So, I want to analyze which JavaScript is loading on my page. To do this, I go to the developer tools section, specifically the Network tab, and filter by .js. When reloading the page, I can clearly see the loaded scripts, and we'll see that the JS part is CRITICAL, as it loads too many resources, slowing down the loading of the entire website.
How I Went from a Performance of 37 to 100 in PageSpeed: The <iframe> Problem
In this and the next videos, I'm going to talk a little about how I went from having a performance of 37 in my posts to simply reaching 100. Which, honestly, still surprises me quite a bit, as I still have a few things to fix. Therefore, even though the metric is now perfect, the site is not entirely perfect. I still have details to improve, as I have noted here, but anyway... I'm going to tell you a bit about everything.
In this first video, I want to focus solely on the blessed YouTube <iframe>, which has been a real poison for performance.
The Embedded Video Dilemma on the Blog
If you have a blog, you've probably already been advised that you should share both text and video, especially if you create mixed content. That is, you create a video and then write an article, or vice versa. The ideal is for both to be presented in the same publication: the video above and the text below. This way, you are enhancing your content by offering two different formats.
So far, so good. The problem comes when you decide to embed the video directly with the YouTube <iframe>, which is truly garbage in terms of performance.
That was the cancer my blog had, so I want to talk a little about that.
Why the Native YouTube iframe is Poison for PageSpeed
When I analyzed my posts with DevTools, I noticed that the iframe loaded a ton of YouTube scripts, even if the user didn't even watch the video. That killed my LCP (Largest Contentful Paint).
It was one of the big culprits of the famous "37" I had at the beginning.
Solution: WebP Thumbnail + On-Demand Player
What I do now is super simple:
I load a WebP image (local, lightweight).
When the user clicks, only then do I load the actual iframe.
The video appears as if it had always been there.
Result:
Ultra-fast loading
Zero blocking YouTube JS
Clean experience
The most ironic thing is that YouTube gives you a JPG as a thumbnail... but Google penalizes you if you don't use WebP. Excellent.
How to Recover Rich Data with JSON-LD (VideoObject) - Structured Data
If you remove the native iframe, Google no longer automatically detects your video. The solution: use JSON-LD.
And that's exactly what I didn't want. I prefer to keep the content as everyone does—using <iframe>—and then process it internally. But if you want to go the plugin route, that's also valid.
Identification of My JavaScript
At this point, we are clear about the scripts that are not part of the website, such as Facebook pixel or Google ADS, and we also know about other resources like YouTube that, although not part of the website, are necessary to have a more interesting resource.
The next thing we look for is what we can optimize from our own JS files. In this case, there are two:
highlight.js
blog.js
Image, Multimedia, and DOM Optimization
These two are my own creation. This is already an improvement because I used to have everything in a single file, the famous blog.js. This included everything. Based on recommendations, I decided to divide them into smaller modules.
I am also eliminating dependencies that I no longer need. For example, Axios: on the blog, I only make a few requests, and I have migrated most to fetch. Something might still be lingering there, but I am slowly tidying everything up.
Replacement of Axios and other scripts
I completely eliminated Axios. I also considered removing Alpine, but it weighs so little that I will leave it for now.
Another script I am evaluating is the highlight.js plugin. This is fundamental for me, as it formats the code in the articles. Although I don't have a visible example here, it basically detects pre blocks and applies a visual format to them, separating them by tokens to style the code.
Code Organization and Conditional Loading
What I am doing is organizing the code better. For example, I have highlight.js divided: its CSS is in the main layout, and the JS is loaded only when necessary, under a specific condition. I will explain that logic in a future video.
The blog.js file now only contains the minimum functions: searches and local requests. It has less than 300 lines, and I plan to optimize it even more. It only keeps the essential.
BIG TRICK for SEO in Laravel, Deferred Loading, and Content Pre-Rendering
▶
I was explaining how I went from a score of 50 to 73 in PageSpeed, simply by changing the blessed YouTube iframe. This is what I had initially, and notice that when I change it, I now have 73 here. Now I will continue the explanation from that point and show you how I reached 100, with the changes I made.
Real Example: How I Went from 37 to 100 in PageSpeed
I also take this opportunity to mention that this material will be part of a course that I will create later. I am currently working on my online store—as of the recording date—but later I will do another course on how to create a well-optimized blog, explaining all these details and a few more things.
This is the point where everything fits together. The path was more or less like this:
Eliminated Axios
Divided my JS
Eliminated dashboard scripts
Implemented WebP thumbnails for YouTube
Added JSON-LD to recover lost SEO
Improved accessibility
Reorganized headings
Integrated deferred loading for Alpine, highlight.js, and other scripts
Technical SEO in Laravel: Essential Tasks that Google Really Evaluates
Improving Accessibility, title, alt, and Derivatives
I also made adjustments to the images, ensuring I placed the alt attribute with an adequate description. And something very important: respecting the hierarchy of headings. If you place an H1, the next one should be an H2, then H3, and so on. You should not jump directly from an H1 to an H3 because the content logic is broken. That was one of the details that was being pointed out to me.
Laravel makes it easy to apply real technical SEO if you know where to look.
XML Sitemap with Spatie
It is practical, simple, and completely automatic.
robots.txt, Canonicals, and Indexability
Laravel doesn't touch this by default, but creating a dynamic robots.txt is simple. Canonical URLs must be generated automatically based on the route.
Security and HTTPS
HTTPS is mandatory. Laravel facilitates all handling of CSRF, secure headers, and best practices.
Accessibility and Semantics
From there, the rest of the changes focused on making the loading more efficient. That included things like improving accessibility, for example, by placing special attributes on buttons to indicate their function. This is especially useful for screen readers, such as those used by people with visual disabilities, so they understand what each button does:
Buttons do not have accessible names Below are tips to improve the semantics of the controls in your application. 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 this, and so does Google.
Recommended SEO Packages for Laravel Projects
spatie/laravel-sitemap → automatic sitemap.xml
spatie/schema-org → clean JSON-LD
artesaos/seotools → comprehensive meta tags
laravelium/sitemap → lightweight alternative
All of these help, but none replace optimizing JS, images, and HTML.
Optimizing Blocking Resources
In addition, I optimized the server response. Although changing the iframe helped a lot, I also started to remove all unnecessary JavaScript. There were things loading from the dashboard, senselessly. I eliminated all of that.
JavaScript Reduction and Cleaner Code
The JavaScript that was loading affected performance quite a bit. Here you can see what the YouTube iframe was taking: a huge amount. And all these small changes directly affect how the page is rendered.
As you solve these problems, the loading improves, the server responds faster, and everything is processed more efficiently. That's why, by making a few strategic changes, the website evolved significantly.
The Problem of Excessive DOM
One of the aspects that Google also flagged for me was the excessive size of the DOM. This is because, for example, in my content, I have two main types: one with the YouTube video and its transcription, and another with the courses.
When you open a course, the document is long because it contains all the course sections with their description. That is, I place all that structure within the HTML so that Google understands what each thing does. That's why the DOM is so long.
This comes directly from my course platform, where each section has its classes. I simply copied that structure to the blog post. So, even though it's long, it makes sense and is useful content.
Formatting and Accessibility Issues
There are some visual details that I also need to improve. For example, some contrasts are too strong. I put a darker background, but maybe it's too much. I also need to reorganize the listing so that recent posts appear first.
And something curious: when I use YouTube images, they return a JPG, not a WebP, which is what Google prefers. And this is despite the fact that if you upload an image to YouTube and right-click to download it, you often get a WebP. I don't know why they don't do it by default... it seems like they do it on purpose to complicate our lives.
JavaScript Reduction: Deeper Dive Returning to the key aspects: reducing JavaScript was essential. Also, taking care of names and attributes. For example, images must have their alt attribute, and buttons must have their aria-label, which I didn't even know about, but which serves to indicate what each button does.
What matters most to me to explain is how I kept everything as natural as possible. If a post has a YouTube video, I simply place the iframe and that's it.
Source Code and Highlight.js
This blog is for programmers, so obviously there are code snippets. I am working in parallel with the books, which have a lot of code. For that, I use highlight.js, which styles the code and makes it look nice.
The problem is that when you have a book with 300 pages, the plugin becomes heavy. At some point, it stops working or slows down a lot. That also affects the page load.
That's why I did something key: process the content once and save it in a field called final_content. That means that Highlight.js only runs once, and then it is no longer needed. This saves me the entire load of the plugin.
Alpine.js and Minimal Scripts
Another part I optimized was script loading. I use Alpine.js for the hamburger menu and share buttons. But even though it weighs little (about 8KB), I decided to defer its loading as well.
What is the minimum that my website needs to load? The CSS, no doubt. The HTML alone is not enough. Also the CSS for the plugin that styles the code. Google doesn't like abrupt style changes, which is why that CSS must be loaded from the beginning.
Common Mistakes That Ruin SEO in Laravel
Loading GTM, Ads, or Facebook Pixel too early.
Jumping from an H1 to an H3 without going through an H2.
Embedding videos without optimizing them.
Leaving repeated or unnecessary scripts.
Not controlling alt or aria-label attributes.
Serving heavy images or images without WebP.
Laravel SEO, some packages to generate meta tags
▶
There are multiple packages to generate SEO tags for Laravel, this is great since there are certain parts of the application that need to return some met tags exactly where we need them, without worrying about whether the names of these tags are correct or not, it already generates them the package by us:
SEOTools
▶
Metatags are an essential element to give metadata or data to search engines, particularly search spiders like Google and that can obtain data about our web page, elements such as the title (which is not really a metatag), description, image, among others, are essential to give the first impression of our site and know what to do with it, therefore, as the development of Laravel go hand in hand for the creation of these types of sites, we will know how we can perform this integration without dying in the attempt.
There are multiple types of metadata that we can use, which in practice are HTML tags that are used to provide data, a description, a title, a reference to an image that alludes to the post (usually the famous main image of the post) before the so-called keywords or keywords that now no longer make much sense for engines like Google... and well, we can also customize some with the most famous social networks such as Twitter or Facebook using the prefixes og, twitter... in the aforementioned tags but in the end we put 3 types of data mainly:
Qualification
Description
Image
This is a package that allows you to easily generate meta tags by using methods:
It will create the configuration file config/seotools.php In the configuration file, you can define default values for SEO meta tags. Open the config/seotools.php file and edit the values to set this value as the default.
With this, we can now use methods to generate the labels such as:
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.");
<title>Latest posts</title><metaname="description"content="Here you will find the latest posts that I have uploaded to my blog."><metaproperty="og:title"content="Latest posts"><metaproperty="og:type"content="articles"><metaproperty="og:description"content="Here you will find the latest posts that I have uploaded to my blog."><metaname="twitter:title"content="Latest posts"><metaname="twitter:site"content="@LibreDesarrollo"><metaname="twitter:description"content="Here you will find the latest posts that I have uploaded to my blog."><scripttype="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>
Another example: you can delete or comment out the ones you don't want to use:
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 these permanently, for a particular block in which I show the latest publications; but, for example, for my post:
Already part of the content is dynamic, which makes up the post that is being displayed.
In the blade file, or master
Now, we can reference a blade file, which in most cases would be our template in Laravel to add the position where we are going to add the metas, which would generally be in the head:
Then, we'll run the scanner on two separate demos based on the first two packages.
1. Artisa SEO Tools
The first package, SEO Tools, works like this: we have a simple demo page, and what it does is that if we inspect a post in the source code, we'll see tags like meta description, meta Twitter, title, among others.
To populate this data from the database, I created an administration panel with Filament inside the same project. For example, we can fill in title, description, and author for a post, save the changes, and in the database, it will be reflected in the table I created specifically for SEO, outside of the package.
Upon refreshing the page, we'll see the titles and metas in the source code, including OG title and OG description.
Additionally, default values can be configured through a configuration file, including title, Open Graph, Twitter, among others.
This package is old (with commits from nine years ago), but it's still compatible with Laravel 12. The last update on March 14th adapted the package for this Laravel version.
2. Ralph J. Smith Laravel SEO
The second package is Ralph J. Smith Laravel SEO. Ralph is very active in the Filament community, which is why part of this package includes functionalities for Filament.
In its main documentation, it indicates that in the Blade file, we only need to render SEO for a post, and to get the data from the model, hasSEO is used on the Eloquent model. This automatically adds the relationship in the database, unlike the first package, where we manually created the relationship.
We can change title, description, etc., directly from the panel, and the changes are reflected on the page, without the need to manually manipulate the relationships as in the first package.
Both packages allow working with meta tags, titles, and SEO tags in the HTML, which is still relevant for classic SEO in search engines.
3. Laravel SEO Scanner
The third package is Laravel SEO Scanner, which allows you to scan your site's pages and check the SEO configuration according to different criteria.
For example, after installing it in the first demo project, we run seo:scan and get results such as: 19 successful checks and 6 failed ones, indicating which elements are missing, like Open Graph image, meta description, or keywords.
Conclusion
Of the three packages:
Two allow configuring metadata (Artisa SEO Tools and Ralph J. Smith SEO).
One is used to scan the pages (Laravel SEO Scanner).
Conclusion: Keeping a Laravel Project Fast, Clean, and Well-Positioned
SEO in Laravel is not just about "adding keywords." It is a continuous technical process. You must review your scripts, images, structure, and integrations. Small changes add up, and sometimes a single iframe can knock everything down.
The key is to:
simplify
load only the essential
organize your HTML
constantly monitor
If you do it right, you can achieve results like the leap I made: from 37 to 100. And most importantly: maintain that quality in the long term.
Frequently Asked Questions About SEO in Laravel
How to improve PageSpeed in Laravel?
In this guide, you have detailed steps so you can improve the SEO of your website.
What SEO packages can I use?
There are many packages you can use for SEO; I gave you a list above.
How to optimize YouTube in Laravel without losing SEO?
Above, I showed you how you can generate a special JSON to indicate the video's content.
What influences more: content or performance?
Both elements are important.
Is Laravel good for SEO?
Yes, if you optimize the technical aspects.
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.
I agree to receive announcements of interest about this Blog.