Настройка аутентификации в Keystatic CMS (Local mode) в проекте Next.js

5 мая 2026 г.

Довольно часто, при создании лендинга или небольшого сайта, заказчик просит предоставить ему возможность самостоятельного редактирования информации на лендинге/сайте. Нет никаких проблем в том, чтобы прикрепить к лендингу/сайту какую-нибудь CMS. Но тут начинается самое интересное - устанавливать CMS, вроде Strapi, Directus, Payload или даже Wordpress, которые используют для своей работы базу данных, для лендинга или небольшого сайта - это как «из пушки во воробьям стрелять».

База данных нужна там, где необходим поиск, сортировка, выборка и др. функционал, требующий быстрой обработки больших объёмов информации. Кроме того, база данных требует выполнения сложных миграций при изменении структуры лендинга/сайта и тянет за собой увеличение расходов на поддержку, обслуживание, резервное копирование, защиту от несанкционированного доступа или взлома. И всё это ради лендинга или небольшого сайта?!

Да, опытный разработчик, при создании лендинга/сайта, может развернуть базу данных за несколько минут, а по окончании разработки передать готовый продукт заказчику и благополучно обо всём забыть, переключившись на новые задачи. А заказчику потом жить со всем этим «хозяйством» не один месяц и не один год.

К счастью, для таких кейсов есть готовые решения - CMS, не использующие для своей работы базу данных, а сохраняющие данные в файлах специальных форматов - Markdown, YAML, JSON и т.п. Но и тут не всё просто.

Долгое время я пытался найти подходящую CMS, которая соответствует следующим требованиям:

  1. Не использует базу данных.
  2. Не зависит от сторонних сервисов и может работать автономно на хостинге заказчика.
  3. Не требует пересборки сайта после внесения изменений и позволяет обновлять сайт в режиме реального времени.

Не могу утверждать, что я перебрал все существующие в мире CMS, но попыток было много, и все испытанные мною CMS, соответствовали только двум из трёх, перечисленных выше, требований. Даже самые близкие к моим требованиям Decap CMS и Pages CMS не умеют работать полностью автономно и требуют для своей работы интеграцию с GitHub.

Единственная CMS, которая соответствует всем трём перечисленным требованиям - это Keystatic CMS, которая может работать в двух режимах - «Github mode» и «Local mode». «Github mode» требует интеграции с CitHub, а вот «Local mode» как раз умеет работать полностью автономно.

К сожалению, Keystatic CMS в режиме «Local mode» пока не имеет встроенной аутентификации. Это означает, что аутентификацию (вход) в CMS необходимо реализовать самостоятельно. А если мы используем CMS без базы данных и без зависимостей от сторонних сервисов, то, логично, чтобы и механизм аутентификации не использовал базу данных и не зависел от сторонних сервисов, иначе какой во всём этом смысл?

Найти библиотеку аутентификации, умеющую работать без базы данных и без зависимостей от сторонних сервисов, оказалось гораздо проще, чем найти саму CMS. Выбор пал на популярную библиотеку Auth.js потому, что она отлично интегрируется с Next.js.

Настройка

1. Устанавливаем библиотеку next-auth:

terminal
npm install next-auth@beta

2. В корне проекта или в папке src (если она есть) создаём файл proxy.js:

src/proxy.js
export { auth as proxy } from "@/lib/auth";
 
export const config = {
  matcher: "/keystatic/:path*",
};

3. В папке lib создаём файл auth.js:

src/lib/auth.js
import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
 
import { users } from "@/data/users";
 
const credentials = {
  email: {
    type: "email",
    label: "Логин",
  },
  password: {
    type: "password",
    label: "Пароль",
  },
};
 
export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    Credentials({
      credentials: credentials,
 
      authorize: (credentials) => {
        return getUser(credentials);
      },
    }),
  ],
 
  callbacks: {
    authorized: ({ auth }) => {
      return !!auth;
    },
  },
 
  trustHost: true,
});
 
const getUser = (credentials) => {
  if (credentials) {
    const user = users[credentials.email];
 
    if (user && user.password === credentials.password) {
      return {
        name: user.name,
        email: credentials.email,
      };
    }
  }
 
  return null;
};

4. И, наконец, список пользователей:

src/data/users.js
export const users = {
  "gorbunkov@mail.ru": {
    name: "Семён Семёныч Горбунков",
    password: "...",
  },
  "bunsha@mail.ru": {
    name: "Иван Васильевич Бунша",
    password: "...",
  },
};