Пагинация новостей в Laravel 11: короткое руководство
За 20 минут выведем ленту новостей постранично: от миграции и модели до готовых ссылок на страницы в Blade. Разберём paginate, simplePaginate и cursorPaginate — и когда какой брать.

К концу этого руководства у вас будет страница /news, которая показывает новости по 10 штук с рабочими ссылками «назад / вперёд» внизу. Займёт минут двадцать, если Laravel уже установлен. Всё написано для Laravel 11.x на PHP 8.2+; в Laravel 10 код тот же, отличаются только мелочи стартового скелета проекта.
Пагинация в Laravel — это не отдельная библиотека, а метод у построителя запросов и у Eloquent. Вы вызываете paginate(10) вместо get(), а фреймворк сам читает номер страницы из параметра ?page=, добавляет LIMIT и OFFSET в SQL и отдаёт объект, который умеет рисовать ссылки. Ничего вручную считать не нужно.
Что нужно иметь до начала
- Установленный проект Laravel 11 и запущенный
php artisan serve(или ваш локальный домен). - Настроенное подключение к БД в файле
.env— строкиDB_CONNECTION,DB_DATABASEи остальные. Проверьте, чтоphp artisan migrateотрабатывает без ошибок. - Node и сборка фронтенда, если хотите стили Tailwind у ссылок пагинации. Без сборки ссылки всё равно работают, просто выглядят голым HTML.
Если php artisan выдаёт ошибку подключения к базе — сначала чините .env, дальше идти смысла нет.
Шаг 1. Модель, миграция и тестовые данные
Создадим модель News сразу с миграцией и фабрикой:
php artisan make:model News -mf
Флаг -m создаёт миграцию, -f — фабрику. Откройте свежую миграцию в database/migrations/ и опишите таблицу:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('news', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->timestamp('published_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('news');
}
};
Опишите фабрику в database/factories/NewsFactory.php, чтобы было что листать:
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class NewsFactory extends Factory
{
public function definition(): array
{
return [
'title' => fake()->sentence(6),
'body' => fake()->paragraphs(3, true),
'published_at' => fake()->dateTimeBetween('-30 days', 'now'),
];
}
}
Прогоните миграцию и накидайте 50 записей через tinker:
php artisan migrate
php artisan tinker --execute="App\Models\News::factory(50)->create();"
Проверка: команда php artisan tinker --execute="echo App\Models\News::count();" должна вывести 50.
Шаг 2. Контроллер и маршрут
Создайте контроллер:
php artisan make:controller NewsController
В app/Http/Controllers/NewsController.php опишите метод index:
<?php
namespace App\Http\Controllers;
use App\Models\News;
class NewsController extends Controller
{
public function index()
{
// 10 новостей на страницу, свежие сверху
$news = News::orderByDesc('published_at')->paginate(10);
return view('news.index', ['news' => $news]);
}
}
Зарегистрируйте маршрут в routes/web.php:
use App\Http\Controllers\NewsController;
Route::get('/news', [NewsController::class, 'index'])->name('news.index');
Проверка: откройте /news. Пока увидите ошибку «View [news.index] not found» — это нормально, шаблон делаем следующим шагом.
Шаг 3. Blade-шаблон и ссылки пагинации
Создайте файл resources/views/news/index.blade.php:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Новости</title>
</head>
<body>
<h1>Новости</h1>
@foreach ($news as $item)
<article>
<h2>{{ $item->title }}</h2>
<p>{{ Str::limit($item->body, 200) }}</p>
<small>{{ $item->published_at?->format('d.m.Y') }}</small>
</article>
<hr>
@endforeach
{{-- Ссылки на страницы --}}
{{ $news->links() }}
</body>
</html>
Метод links() и есть вся магия: он рисует блок ссылок «1 2 3 … Next». По умолчанию разметка ссылок рассчитана на Tailwind. Если Tailwind в проекте нет, ссылки будут без стилей, но кликаться будут.
Проверка: на /news видно 10 новостей и блок пагинации внизу. Клик по «2» или переход на /news?page=2 показывает следующую десятку.
Переключить разметку на Bootstrap
Если у вас Bootstrap, а не Tailwind, скажите Laravel об этом один раз в app/Providers/AppServiceProvider.php, в методе boot():
use Illuminate\Pagination\Paginator;
public function boot(): void
{
Paginator::useBootstrapFive();
}
Есть и useBootstrapFour(). Если нужен полный контроль над HTML — опубликуйте шаблоны командой php artisan vendor:publish --tag=laravel-pagination и правьте их в resources/views/vendor/pagination/.
Три способа пагинации и чем они платят
Laravel даёт три метода. Разница не косметическая — она в том, какой SQL уходит в базу.
| Метод | Что рисует | Запрос считает total? | Когда брать |
|---|---|---|---|
paginate() | Номера страниц + «назад/вперёд» | Да, отдельный COUNT(*) | Нужны номера страниц и общее число |
simplePaginate() | Только «назад/вперёд» | Нет | Номера не нужны, важна скорость |
cursorPaginate() | Только «назад/вперёд», по курсору | Нет, использует WHERE по ключу | Большие таблицы, бесконечная лента |
Главная цена paginate() — второй запрос COUNT(*) на каждый показ страницы. На таблице в сотню тысяч строк это заметно. Цена OFFSET вырастает с номером страницы: LIMIT 10 OFFSET 100000 заставляет БД пролистать сто тысяч строк, прежде чем отдать десять. Поэтому глубокая пагинация через paginate() и simplePaginate() медленная по своей природе.
Если у вас лента, которую листают вниз, а не таблица с номерами страниц, — берите cursorPaginate(). Он не считает total и не использует OFFSET, поэтому страница №5000 открывается так же быстро, как первая.
cursorPaginate() требует стабильной сортировки по уникальному, желательно индексированному столбцу. Сортировка по published_at, где значения могут совпадать, требует добавить в порядок ещё и id, иначе часть записей потеряется или задвоится на стыке страниц:
$news = News::orderByDesc('published_at')
->orderByDesc('id')
->cursorPaginate(10);
Сохранить фильтры в ссылках
Типичная ошибка: добавили фильтр ?category=tech, кликнули «страница 2» — фильтр слетел. Ссылки пагинации по умолчанию тащат только page. Чтобы дописать остальные параметры запроса, вызовите withQueryString():
$news = News::query()
->when(request('category'), fn ($q, $cat) => $q->where('category', $cat))
->orderByDesc('published_at')
->paginate(10)
->withQueryString();
Теперь ссылки будут вида /news?category=tech&page=2.
Если не получилось
- «View [news.index] not found» — путь к шаблону не совпадает с
view('news.index'). Файл должен лежать вresources/views/news/index.blade.php. - Ссылки пагинации показываются, но без стилей — Tailwind не собран. Либо соберите фронтенд, либо переключитесь на Bootstrap через
useBootstrapFive(), либо опубликуйте и настройте шаблоны пагинации. - «Call to a member function links() on array» — вы где-то вызвали
get()илиall()вместоpaginate(), и в шаблон пришёл обычный массив, а не пагинатор. - На
cursorPaginate()ошибка о неоднозначном курсоре или пропадают записи — сортировка идёт по неуникальному столбцу. Добавьте вorderByуникальный ключ, обычноid. - Номер страницы игнорируется, всегда первая — проверьте, что не переопределили имя параметра. По умолчанию это
page; сменить можно вторым аргументом:paginate(10, ['*'], 'p').
Что дальше
Рабочая лента готова. Дальше стоит посмотреть в сторону кеширования первой страницы (её открывают чаще всего) и добавить индекс на столбец сортировки — без индекса даже cursorPaginate() будет читать таблицу целиком. Если фронтенд отдельный, а Laravel работает как API — там вместо links() отдавайте $news прямо в JSON-ответе: пагинатор сам добавит поля data, links и meta. Инвалидация кеша ленты при добавлении новости — отдельная тема, здесь её не разбираем.
Репетитор 1—11 класс и ВУЗ: Математика и физика методом наведения на мысль, помощь с ДЗ.
Спросить за 8 ₽Источники: Laravel 11 — Database: Pagination (официальная документация), Laravel 11 — Eloquent: Pagination
Частые вопросы
У меня Laravel 10, а не 11 — код подойдёт?
Как поменять число новостей на странице?
cursorPaginate теряет записи на стыке страниц — почему?
Почему на большой таблице страница открывается медленно?
Как отдать пагинацию в JSON для фронтенда на Vue или React?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.