When it comes to Laravel, a leading PHP framework, implementing 301 redirects and tracking clicks is easier than ever. Laravel provides robust features and well-structured libraries to make this process efficient and straightforward. This article will explore how to change URLs, redirect old URLs to new ones, and track clicks using Laravel.
What is a 301 Redirect?
A 301 redirect is the most efficient and search engine-friendly method for webpage redirection. It's the best way to ensure that users and search engines are directed to the correct page.
Implementing 301 Redirects in Laravel
Run the following command to create a new migration file:
Note: Replace article with your own table name
php artisan make:migration create_article_url_changes_table
Now, open the generated migration file. It will be located under database/migrations/
. You'll see two methods in the migration file: up()
and down()
. The up()
method is used to create the table, and the down()
method should reverse the actions of up()
.
Replace the content of the up()
method and add the down()
method as follows
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateArticleUrlChangesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('article_url_changes', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('article_id');
$table->string('old_slug');
$table->string('new_slug');
$table->unsignedBigInteger('redirect_count')->default(0);
$table->timestamps();
$table->foreign('article_id')
->references('id')
->on('articles') // Replace with your own table name
->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('article_url_changes');
}
}
After you've added this code, run the migration:
php artisan migrate
Add this below code to your existing code to
namespace App\Http\Controllers;
use App\Models\Article;
use App\Models\ArticleURLChange;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class ArticleController extends Controller
{
/**
* Handle changes in the article slug.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function handleSlugChange(Request $request): Response
{
$newSlug = $request->input('slug');
$articleId = $request->input('article_id');
// Validate the input data
if (!$this->validateSlugChange($newSlug, $articleId)) {
return response('Invalid data provided', 400);
}
// Retrieve the original model from the database
$originalArticle = Article::find($articleId);
$oldSlug = $originalArticle->slug;
// Check and handle the slug change
if (!$this->shouldUpdateSlug($oldSlug, $newSlug)) {
return response('Slug has not changed', 200);
}
$this->updateSlug($articleId, $oldSlug, $newSlug);
return response('Slug updated successfully', 200);
}
/**
* Validate the slug and article ID.
*
* @param string $newSlug
* @param int $articleId
* @return bool
*/
private function validateSlugChange(string $newSlug, int $articleId): bool
{
return !empty($newSlug) && !empty($articleId);
}
/**
* Determine if the slug should be updated.
*
* @param string $oldSlug
* @param string $newSlug
* @return bool
*/
private function shouldUpdateSlug(string $oldSlug, string $newSlug): bool
{
return $oldSlug !== $newSlug;
}
/**
* Update the slug in the database.
*
* @param int $articleId
* @param string $oldSlug
* @param string $newSlug
* @return void
*/
private function updateSlug(int $articleId, string $oldSlug, string $newSlug): void
{
// Check if there's already a redirect from the new slug to the old slug
$existingRedirect = ArticleURLChange::where('old_slug', $newSlug)
->where('new_slug', $oldSlug)
->first();
// Delete the old redirect if we are reverting back to an old slug
if ($existingRedirect) {
$existingRedirect->delete();
}
// Create a new redirect entry
ArticleURLChange::create([
'article_id' => $articleId,
'old_slug' => $oldSlug,
'new_slug' => $newSlug,
]);
}
}
In the below example, I used a method named handleSlugChange
to encapsulate the logic. This method accepts a route object, which will only contain the incoming HTTP when main Article Route doesn't find any result. You can call implement is as per your choice. I Didn't want to implement it as a middleware to avoid making it run everytime.
Route::get('/{article:slug}', \App\Http\Controller\ArticleDetail::class)->name('blog.articles.detail')
->missing(function ($request) {
$urlChange = ArticleURLChange::where('old_slug', $request->route('article'))->first();
if ($urlChange) {
$urlChange->increment('redirect_count');
return redirect('/' . $urlChange->new_slug, 301);
}
abort(404);
});
Full-stack developer with a knack for Merging creativity with technical expertise for standout solutions.