• Call Us: +92-333-7276335
  • - Mail Us: info@shekztech.com

Plot 1177, Sector 31B - Crossing, Karachi, Sindh

Loading...
How to creat the SEO functionality in laravel 10 for your application
  • Views: 537
  • Category: Laravel
  • Published at: 01 Sep, 2023
  • Updated at: 07 Sep, 2023

How to creat the SEO functionality in laravel 10 for your application

Search Engine Optimization (SEO) is a crucial aspect of any modern web application. While Laravel 10 doesn't include built-in SEO functionalities, implementing them properly can drastically improve your site's visibility, user experience, and organic search rankings. By allowing precise control over meta tags, page titles, and other SEO-related parameters, you can better align your web application with Google's and other search engines' algorithms. While there are numerous third-party packages available for SEO in Laravel, they may not always offer the level of customization and control you may require. These packages can also introduce additional dependencies and potential security risks to your project.


The disadvantages of using third-party SEO packages include limited customization, the need to learn how the package works, and sometimes even a performance overhead. There are also instances where you may require specific SEO features that a third-party package doesn't support, thus necessitating custom implementation anyway. On the other hand, crafting your SEO functionality gives you the advantage of full customization, direct control over data, and the opportunity to optimize performance. By defining your own SEO routes, middleware, and logic, you can tailor the SEO functionalities to perfectly match your application's specific needs.

Certainly! Let's go step-by-step to create an SEO functionality in a Laravel 10 application. In this tutorial, I'll walk you through creating a simple SEO management system to store and manage SEO data for various pages on your website.

Prerequisites

 

Step 1: Create SEO Table Migration

You've already done this part, but let's go over it. Run the following Artisan command to create a new migration:

php artisan make: migration create_seo_table
public function up(): void
    {
        Schema::create('seo', function (Blueprint $table) {
            $table->id();
            $table->foreignId('admin_id')->constrained('users')->onDelete('cascade');
            $table->text('page_url');
            $table->string('page_url_hash')->unique();
            $table->text('old_page_url')->nullable();
            $table->string('title');
            $table->text('meta_description');
            $table->text('meta_keywords')->nullable();
            $table->text('canonical_url')->nullable();
            $table->string('robots')->nullable();
            $table->json('og_tags')->nullable();
            $table->json('twitter_tags')->nullable();
            $table->json('courses_schema')->nullable();
            $table->json('blog_schema')->nullable();
            $table->string('hreflang')->nullable();
            $table->text('footer_text')->nullable();
            $table->timestamps();
            $table->softDeletes();
        });
    }

   
    public function down(): void
    {
        Schema::dropIfExists('seo');
    }

You have an SEO table with columns like page_url, title, meta_description, and so on. This table is linked to the users table via a foreign key admin_id.

Step 2: Create SEO Model

Again, you've already done this. This step involves creating the SEO model that will interact with the SEO table. The fillable array contains the fields that you'll be allowing to mass-assign data into.

php artisan make: model SEO

In your SEO model, the $fillable property allows the following fields to be mass-assignable.

Step 3: Create an SEO Controller

Let's create a new controller to manage SEO-related CRUD operations:

php artisan make:controller AdminController

Step 4: Create Routes

In your routes/web.php, add the following route:

Route::post('upsert-seo', [AdminController::class, 'upsertSEO'])->name('seo.upsert');

Step 5: Create upsertSEO Function in AdminController

Create a method in AdminController to handle the upsertSEO action: 

public function upsertSEO(Request $request)
    {

        $request->validate([
            'page_url' => [
                'required',
                'max:1000',
            ],
            'old_page_url' => 'nullable|max:1000',
            'title' => 'required|string|max:255',
            'meta_description' => 'required|string|max:500',
            'meta_keywords' => 'nullable|string',
            'canonical_url' => 'nullable|max:1000',
            'robots' => 'nullable|string|max:255',
            'og_tags' => 'nullable|array',
            'twitter_tags' => 'nullable|array',
            'hreflang' => 'nullable|string|max:255',
            'footer_text' => 'nullable|string',
        ]);

        $page_url_hash = hash('sha256', $request->page_url);


        $seoData = [
            'page_url' => $request->input('page_url'),
            'old_page_url' => $request->input('old_page_url'),
            'title' => $request->input('title'),
            'meta_description' => $request->input('meta_description'),
            'meta_keywords' => $request->input('meta_keywords'),
            'canonical_url' => $request->input('canonical_url'),
            'robots' => $request->input('robots'),
            'og_tags' => json_encode($request->input('og_tags')),
            'twitter_tags' => json_encode($request->input('twitter_tags')),
            'hreflang' => $request->input('hreflang'),
            'footer_text' => $request->input('footer_text'),
            'admin_id' => Auth::id(),
        ];
        //dd($seoData);
        SEO::updateOrCreate(
            ['page_url_hash' => $page_url_hash],
            $seoData
        );

        $redirectUrl = $request->input('redirect_url') ?: 'admin.dashboard';

        return redirect($redirectUrl)->with('success', 'SEO record created or updated successfully!');
    }

Step 6: Blade Template and Form

Your Blade form is comprehensive and makes use of Laravel's old input functionality, error handling, and CSRF protection. This form sends a POST request to upsertSEO

<div class="container" id="seoSection">
    <div class="row">
        <div class="col">
            <div class="container pt-5 pb-5" id="seoSection">
                <div class="row">
                    <div class="col">
                        <form action="{{ route('admin.seo.upsert') }}" method="POST">
                            @csrf
                            <div class="row">
                                <div class="col-md-6 form-group">
                                    <label for="page_url">Page URL*</label>
                                    <input type="text" id="page_url" name="page_url" class="form-control" value="{{ old('page_url', $seo->page_url ?? request()->url()) }}" required>
                                    @error('page_url')
                                    <span class="text-danger">{{ $message }}</span>
                                    @enderror
                                </div>
                                <div class="col-md-6 form-group">
                                    <label for="old_page_url">Old Page URL</label>
                                    <input type="text" id="old_page_url" name="old_page_url" class="form-control" value="{{ old('old_page_url', $seo->old_page_url ?? '') }}" >
                                    @error('old_page_url')
                                    <span class="text-danger">{{ $message }}</span>
                                    @enderror
                                </div>
                            </div>

                            <div class="row">
                                <div class="col-md-6 form-group">
                                    <label for="title">Title*</label>
                                    <input type="text" id="title" name="title" class="form-control" value="{{ old('title', $seo->title ?? '') }}" required>
                                    @error('title')
                                    <span class="text-danger">{{ $message }}</span>
                                    @enderror
                                </div>
                                <div class="col-md-6 form-group">
                                    <label for="meta_keywords">Meta Keywords*</label>
                                    <input type="text" id="meta_keywords" name="meta_keywords" class="form-control" value="{{ old('meta_keywords', $seo->meta_keywords ?? '') }}" required>
                                    @error('meta_keywords')
                                    <span class="text-danger">{{ $message }}</span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group">
                                <label for="meta_description">Meta Description*</label>
                                <textarea id="meta_description" name="meta_description" class="form-control" required>{{ old('meta_description', $seo->meta_description ?? '') }}</textarea>
                                @error('meta_description')
                                <span class="text-danger">{{ $message }}</span>
                                @enderror
                            </div>

                            <div class="row">
                                <div class="col-md-6 form-group">
                                    <label for="canonical_url">Canonical URL</label>
                                    <input type="text" id="canonical_url" name="canonical_url" class="form-control" value="{{ old('canonical_url', $seo->canonical_url ?? '') }}">
                                    @error('canonical_url')
                                    <span class="text-danger">{{ $message }}</span>
                                    @enderror
                                </div>
                                <div class="col-md-6 form-group">
                                    <label for="og_title">OG Title</label>
                                    <input type="text" id="og_title" name="og_tags[title]" class="form-control" value="{{ old('og_tags.title', $seo->og_tags['title'] ?? '') }}">
                                    @error('og_tags.title')
                                    <span class="text-danger">{{ $message }}</span>
                                    @enderror
                                </div>
                            </div>

                            <div class="row">
                                <div class="col-md-6 form-group">
                                    <label for="og_image">OG Image</label>
                                    <input type="text" id="og_image" name="og_tags[image]" class="form-control" value="{{ old('og_tags.image', $seo->og_tags['image'] ?? '') }}">
                                    @error('og_tags.image')
                                    <span class="text-danger">{{ $message }}</span>
                                    @enderror
                                </div>
                                <div class="col-md-6 form-group">
                                    <label for="og_description">OG Description</label>
                                    <input type="text" id="og_description" name="og_tags[description]" class="form-control" value="{{ old('og_tags.description', $seo->og_tags['description'] ?? '') }}">
                                    @error('og_tags.description')
                                    <span class="text-danger">{{ $message }}</span>
                                    @enderror
                                </div>
                            </div>

                            <div class="row">
                                <div class="col-md-6 form-group">
                                    <label for="twitter_site">Twitter Site</label>
                                    <input type="text" id="twitter_site" name="twitter_tags[site]" class="form-control" value="{{ old('twitter_tags.site', $seo->twitter_tags['site'] ?? '') }}">
                                    @error('twitter_tags.site')
                                    <span class="text-danger">{{ $message }}</span>
                                    @enderror
                                </div>
                                <div class="col-md-6 form-group">
                                    <label for="twitter_title">Twitter Title</label>
                                    <input type="text" id="twitter_title" name="twitter_tags[title]" class="form-control" value="{{ old('twitter_tags.title', $seo->twitter_tags['title'] ?? '') }}">
                                    @error('twitter_tags.title')
                                    <span class="text-danger">{{ $message }}</span>
                                    @enderror
                                </div>
                            </div>

                            <div class="row">
                                <div class="col-md-6 form-group">
                                    <label for="twitter_description">Twitter Description</label>
                                    <input type="text" id="twitter_description" name="twitter_tags[description]" class="form-control" value="{{ old('twitter_tags.description', $seo->twitter_tags['description'] ?? '') }}">
                                    @error('twitter_tags.description')
                                    <span class="text-danger">{{ $message }}</span>
                                    @enderror
                                </div>
                                <div class="col-md-6 form-group">
                                    <label for="twitter_image">Twitter Image</label>
                                    <input type="text" id="twitter_image" name="twitter_tags[image]" class="form-control" value="{{ old('twitter_tags.image', $seo->twitter_tags['image'] ?? '') }}">
                                    @error('twitter_tags.image')
                                    <span class="text-danger">{{ $message }}</span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group">
                                <label for="hreflang">Hreflang</label>
                                <select id="hreflang" name="hreflang" class="form-control">
                                    <option value="">Please select...</option>
                                    <option value="en-US" {{ old('hreflang', $seo->hreflang) == 'en' ? 'selected' : '' }}>English</option>
                                    <option value="en-US" {{ old('hreflang', $seo->hreflang) == 'en-US' ? 'selected' : '' }}>English (US)</option>
                                    <option value="en-GB" {{ old('hreflang', $seo->hreflang) == 'en-GB' ? 'selected' : '' }}>English (UK)</option>
                                    <option value="en-CA" {{ old('hreflang', $seo->hreflang) == 'en-CA' ? 'selected' : '' }}>English (Canada)</option>
                                    <option value="en-AU" {{ old('hreflang', $seo->hreflang) == 'en-AU' ? 'selected' : '' }}>English (Australia)</option>
                                    <option value="fr-FR" {{ old('hreflang', $seo->hreflang) == 'fr-FR' ? 'selected' : '' }}>French (France)</option>
                                    <option value="fr-CA" {{ old('hreflang', $seo->hreflang) == 'fr-CA' ? 'selected' : '' }}>French (Canada)</option>
                                    <option value="es-ES" {{ old('hreflang', $seo->hreflang) == 'es-ES' ? 'selected' : '' }}>Spanish (Spain)</option>
                                    <option value="es-MX" {{ old('hreflang', $seo->hreflang) == 'es-MX' ? 'selected' : '' }}>Spanish (Mexico)</option>
                                    <option value="de-DE" {{ old('hreflang', $seo->hreflang) == 'de-DE' ? 'selected' : '' }}>German (Germany)</option>
                                    <option value="de-AT" {{ old('hreflang', $seo->hreflang) == 'de-AT' ? 'selected' : '' }}>German (Austria)</option>
                                </select>
                                @error('hreflang')
                                <span class="text-danger">{{ $message }}</span>
                                @enderror
                            </div>
                            <div class="form-group">
                                <label for="robots">Robots</label>
                                <select id="robots" name="robots" class="form-control">
                                    <option value="index, follow" {{ old('robots', $seo->robots ?? '') == 'index, follow' ? 'selected' : '' }}>True</option>
                                    <option value="noindex, nofollow" {{ old('robots', $seo->robots ?? '') == 'noindex, nofollow' ? 'selected' : '' }}>False</option>
                                </select>
                                @error('robots')
                                <span class="text-danger">{{ $message }}</span>
                                @enderror
                            </div>

                            <input type="hidden" name="redirect_url" value="{{ request()->url() }}">
                            <div class="form-group">
                                <label for="footer_text">Footer Text</label>
                                <textarea id="footer_text" name="footer_text" class="form-control">{{ old('footer_text', $seo->footer_text ?? '') }}</textarea>
                                @error('footer_text')
                                <span class="text-danger">{{ $message }}</span>
                                @enderror
                            </div>

                            <br>
                            <div class="form-group">
                                <button type="submit" class="btn btn-primary">Submit</button>
                            </div>
                        </form>

                    </div>
                </div>
            </div>

        </div>
    </div>
</div>

Note: Thank you for clarifying the purpose of footer_text. In SEO terms, strategically placing text near the footer can indeed serve as a valuable opportunity to use focus keywords, enrich content, or meet a certain text length criteria, thus improving the overall SEO of a webpage.

Explanation of Blade Template

@if(Auth::check() && Auth::user()->role == 'admin'): Ensures that only an authenticated admin can view and submit the form.
@csrf: Adds CSRF token for security.
@error: Displays validation errors for each input field.
{{ old('field', default) }}: Helps to repopulate the form fields if validation fails.

Step 7: Create Middleware for SEO Data

If you want to make the SEO meta-tags appear on every page, you may create a Middleware to handle it.

php artisan make: middleware SEOMiddleware
<?php

namespace App\Http\Middleware;

use App\Models\SEO;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class SEOInjection
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $url = rtrim($request->getUri(), '/');

        // Remove '/public' from the URL
        $url = str_replace('/public', '', $url);


        $seo = SEO::query()
            ->where('page_url', $url)
            ->orWhere('old_page_url', $url)
            ->first();


        // If no SEO data exists for this URL, let the request continue
        if (!$seo) {
            return $next($request);
        }

        // If the current URL matches the old_page_url, redirect to new page_url
        if ($seo->old_page_url == $url) {
            return redirect($seo->page_url, 301);
        }

        // Otherwise, share the SEO data with all views
        view()->share('seo', $seo);

        return $next($request);
    }



}

Then in SEOMiddleware, fetch the SEO data and share it with views.

Don't forget to register your middleware in Kernel.php.

Step 8: Include SEO Data in Layout

Finally, include SEO data in the head section of your layout.

   <title>{{ isset($seo) ? $seo->title : 'Shekz Tech' }}</title>
    <meta name="description" content="{{ isset($seo) ? $seo->meta_description : 'Default Description' }}">
    <meta name="keywords" content="{{ isset($seo) ? $seo->meta_keywords : '' }}">
    @if(isset($seo) && $seo->canonical_url)
        <link rel="canonical" href="{{ $seo->canonical_url }}"/>
    @endif
    <meta name="robots" content="{{ isset($seo) && $seo->robots ? $seo->robots : 'index' }}">
    <link rel="alternate" hreflang="{{ isset($seo) ? $seo->hreflang : 'en' }}" href="{{ isset($seo) && $seo->page_url ? url($seo->page_url) : url()->current() }}">


    @if(isset($seo))
        <meta property="og:title" content="{{ isset($seo->og_tags['title']) && $seo->og_tags['title'] ? $seo->og_tags['title'] : $seo->title }}">
        <meta property="og:description" content="{{ isset($seo->og_tags['description']) && $seo->og_tags['description'] ? $seo->og_tags['description'] : $seo->meta_description }}">
        <meta property="og:image" content="{{ isset($seo->og_tags['image']) ? asset('storage/app/public/images/' . $seo->og_tags['image']) : asset('storage/app/public/images/ogdefault.png') }}">

        <meta name="twitter:title" content="{{ isset($seo->twitter_tags['title']) && $seo->twitter_tags['title'] ? $seo->twitter_tags['title'] : $seo->title }}">
        <meta name="twitter:description" content="{{ isset($seo->twitter_tags['description']) && $seo->twitter_tags['description'] ? $seo->twitter_tags['description'] : $seo->meta_description }}">
        <meta name="twitter:image" content="{{ isset($seo->twitter_tags['image']) ? asset('storage/app/public/images/' . $seo->twitter_tags['image']) : asset('storage/app/public/images/ogdefault.png') }}">
    @endif

Please adjust the view to align with your specific needs and requirements.

And Last thing if you want to add the blog schema for the SEO in your blog page

$blog = $blog->load('user', 'category', 'comments.user');
        $description = substr(strip_tags($blog->description), 0, 160) . '...';
        $logoUrl = asset('assets/myimages/shekztech.png');//update it according to your column
        $schema = [
            "@context" => "http://schema.org",
            "@type" => "BlogPosting",
            "mainEntityOfPage" => [
                "@type" => "WebPage",
                "@id" => url("/blog/{$blog->myblogslug}") //set it to yours
            ],
            "headline" => $blog->title,
            "image" => [asset($blog->image)],
            "datePublished" => Carbon::parse($blog->created_at)->toIso8601String(),
            "dateModified" => Carbon::parse($blog->updated_at)->toIso8601String(),
            "author" => [
                "@type" => "Person",
                "name" => $blog->user->first_name, //update it according to your column
            ],
            "publisher" => [
                "@type" => "Organization", //set it to yours
                "name" => "Shekz Tech",//set it to yours
                "logo" => [
                    "@type" => "ImageObject",
                    "url" => $logoUrl //set it to yours
                ]
            ],
            "description" => $description,
            "articleSection" => $blog->category->name, 
            "wordCount" => str_word_count(strip_tags($blog->description)), 
            "articleBody" => substr(strip_tags($blog->description), 0, 250), 
            "keywords" => "xyz" // update the keywords
        ];
		
		 return view('blogs.singleBlog', [
            'blog' => $blog,
            'schema' => json_encode($schema, JSON_PRETTY_PRINT)
        ]);

You can directly use this code in your "view" in the header. 

@if(isset($schema))
<script type="application/ld+json">
    {!! $schema !!}
</script>
@endif

  If you want to send it to the form and store it in the database then add the below section in your form 

 <div class="form-group">
   <label for="blogschema">Blog Schema</label> <a href="https://search.google.com/test/rich-results" target="__blank">Check Schema</a>
     <textarea id="blogschema" rows="20" cols="20" name="blogschema" class="form-control">{{ stripslashes(old('footer_text', $schemaskz ?? '')) }}</textarea>
                                @error('blogschema')
                                <span class="text-danger">{{ $message }}</span>
                                @enderror
 </div>

Now update the validation 'blogschema' => 'required|string' and add this in the variable "$seoData"  'blog_schema' => $request->input('blogschema'),

 

Conclusion

Creating your own SEO functionality in Laravel 10, as demonstrated in the code example, can offer a more tailored experience for your application. While it requires a more hands-on approach, the advantages of customizability and performance can outweigh the benefits of using a third-party package. Especially in projects where SEO requirements are very specific, a custom-built solution can offer better control and more effective SEO management. Moreover, implementing caching mechanisms, as shown in the example, can further enhance performance, making your SEO operations not just fully customized but also efficient.

 

Shehzad Ahmed

Shehzad Ahmed is a highly qualified expert with a Master of Philosophy in Computer Science and a decade of extensive industry experience. With his impressive track record in web development and computer science, he has not only left an indelible mark on the industry but also made substantial contributions to education. Since , he has created more than eighty exhaustive courses, paving the way for innumerable individuals interested in learning and development. His unparalleled knowledge and innate ability to elucidate complex ideas make him a highly sought-after educator and consultant. Choose Shehzad and take advantage of his exceptional balance of technical expertise and teaching prowess to propel your learning journey or project to new heights.

0 Comment(s)
Write your comment