Категории

Fullstack приложение на Nextjs

🛠️ 1. API (бэкенд внутри Next.js)

📌 Вариант с pages/ (Pages Router)

Создаёшь файлы в pages/api/. Например pages/api/users.js:

// pages/api/users.js
export default function handler(req, res) {
  if (req.method === "GET") {
    res.status(200).json([{ id: 1, name: "John" }]);
  } else if (req.method === "POST") {
    const user = req.body;
    res.status(201).json({ message: "User created", user });
  }
}

Доступно по адресу: 👉 /api/users


📌 Вариант с app/ (App Router, Next 13+)

Здесь API создаётся через app/api/*/route.js:

// app/api/users/route.js
export async function GET() {
  return Response.json([{ id: 1, name: "Alice" }]);
}

export async function POST(req) {
  const data = await req.json();
  return Response.json({ message: "User created", user: data });
}

👉 будет доступно на /api/users.


🛠️ 2. Подключение базы данных

Next не ограничивает — можно воткнуть что угодно:

  • Prisma (SQL/NoSQL, удобный ORM)
  • Mongoose (если MongoDB)
  • Прямой pg драйвер (Postgres)
  • Supabase, Firebase и т.д.

Пример с Prisma и Postgres (app/api/users/route.js):

import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();

export async function GET() {
  const users = await prisma.user.findMany();
  return Response.json(users);
}

export async function POST(req) {
  const data = await req.json();
  const newUser = await prisma.user.create({ data });
  return Response.json(newUser);
}

🛠️ 3. Админка

Тут есть несколько вариантов:

🔹 Собственная админка внутри Next

Просто сделай app/admin/page.js или pages/admin.js, и защищай её авторизацией (например, через NextAuth.js):

// app/admin/page.js
"use client";
import { useSession } from "next-auth/react";

export default function AdminPage() {
  const { data: session } = useSession();

  if (!session) return <p>Access denied</p>;

  return <h1>Welcome to Admin Panel, {session.user.name}</h1>;
}

🔹 Подключение готовых решений

  • React Admin — можно встроить прямо в Next.
  • KeystoneJS или Strapi — как headless CMS + админка (ставятся отдельно, Next просто потребляет API).
  • Sanity — CMS с админкой.

🛠️ 4. Авторизация и безопасность

  • NextAuth.js → самое популярное решение для OAuth, JWT, сессий.
  • Если нужна кастомная auth → можно самому хранить токены/сессии в базе и проверять их в middleware.js.

Подключение админки


📂 1. Модули (утилиты, хелперы, сервисы)

Все твои функции (например, utils.js, api.js, auth.js) лучше складывать в папку lib/ или utils/ в корне проекта. Пример структуры:

my-next-app/
 ├─ app/ (или pages/)
 ├─ components/
 ├─ lib/        <-- утилиты и модули
 │   ├─ api.js
 │   ├─ auth.js
 │   └─ helpers.js
 ├─ public/
 └─ ...

👉 Так принято в Next, и это даёт чистоту в импортах:

import { fetchData } from "@/lib/api";

📂 2. Layout’ы (общие и частичные)

В Pages Router (pages/)

Глобальный layout → pages/_app.js. Частичные (например, DashboardLayout) лучше хранить в components/layouts/.

// components/layouts/DashboardLayout.js
export default function DashboardLayout({ children }) {
  return (
    <div className="dashboard">
      <aside>Sidebar</aside>
      <main>{children}</main>
    </div>
  );
}

Использование:

// pages/dashboard.js
import DashboardLayout from "@/components/layouts/DashboardLayout";

export default function DashboardPage() {
  return (
    <DashboardLayout>
      <h1>Dashboard</h1>
    </DashboardLayout>
  );
}

В App Router (app/)

Layout встроен в сам роут:

app/
 ├─ layout.js      <-- глобальный layout
 ├─ dashboard/
 │   ├─ layout.js  <-- layout только для dashboard
 │   └─ page.js
 └─ about/
     └─ page.js

Пример:

// app/dashboard/layout.js
export default function DashboardLayout({ children }) {
  return (
    <div className="dashboard">
      <aside>Sidebar</aside>
      <main>{children}</main>
    </div>
  );
}

👉 Всё, что внутри /dashboard/* будет обёрнуто этим layout’ом.


📂 3. JSON (данные, конфиги)

Тут варианты зависят от того, для чего JSON:

  • Статичные данные (например, список товаров) → клади в public/data/ или lib/data/.

    import products from "@/lib/data/products.json";

    Если в public/, можно грузить через fetch:

    const res = await fetch("/data/products.json");
    const products = await res.json();
  • Конфиги (например, настройки приложения)config/ или lib/config/.

  • Данные, как API → можно положить JSON в public/ и отдавать через /data/file.json.


📂 Итоговая структура (пример)

my-next-app/
 ├─ app/ (или pages/)      # страницы
 ├─ components/            # UI-компоненты
 │   └─ layouts/           # layout’ы
 ├─ lib/                   # утилиты, модули
 │   ├─ api.js
 │   ├─ helpers.js
 │   └─ data/
 │       └─ products.json
 ├─ public/                # статические файлы (картинки, json для fetch)
 ├─ config/                # конфиги приложения
 ├─ styles/                # CSS/SCSS/Tailwind
 └─ ...

Структура


📂 Структура проекта в Next

app/
 ├─ layout.js          # глобальный layout (NavBar, Footer, <head>)
 ├─ page.js            # /
 ├─ about/
 │   └─ page.js        # /about
 ├─ beats/
 │   └─ page.js        # /beats
 ├─ discography/
 │   └─ page.js        # /discography
 ├─ albums/
 │   └─ page.js        # /albums
 ├─ video/
 │   └─ page.js        # /video
 ├─ contact/
 │   └─ page.js        # /contact
components/
 ├─ NavBar.js
 ├─ Footer.js
layouts/
 ├─ DefaultLayout.js
 └─ HomeLayout.js

📝 Глобальный layout (app/layout.js)

Это твой заменитель App.js:

// app/layout.js
import "./globals.css";
import NavBar from "@/components/NavBar";
import Footer from "@/components/Footer";

export const metadata = {
  title: "Music Artist | Musician. Producer. Beatmaker",
  description: "Music artist website built with Next.js",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <NavBar />
        {children}
        <Footer />
      </body>
    </html>
  );
}

👉 Здесь уже не нужен useEffect для document.title — у Next есть встроенное metadata.


📝 Главная страница с HomeLayout

// app/page.js
import HomeLayout from "@/layouts/HomeLayout";
import Home from "@/pages/Home";

export default function Page() {
  return (
    <HomeLayout>
      <Home />
    </HomeLayout>
  );
}

📝 Другие страницы с DefaultLayout

Например /about:

// app/about/page.js
import DefaultLayout from "@/layouts/DefaultLayout";
import About from "@/pages/About";

export default function Page() {
  return (
    <DefaultLayout>
      <About />
    </DefaultLayout>
  );
}

Точно так же делаешь для /beats, /discography, /albums, /video, /contact.


TS и global.css


✅ Почему так?

TypeScript по умолчанию не знает, что делать с .css / .scss / .svg / .png и т.п. — он умеет проверять только TS/JS. Решение: нужно подсказать компилятору, что эти импорты валидны.


🔹 Решение

Создай в корне проекта файл global.d.ts (или declarations.d.ts) и добавь туда:

// global.d.ts
declare module "*.css";
declare module "*.scss";
declare module "*.sass";

🔹 Проверка tsconfig.json

Убедись, что твой tsconfig.json видит этот файл. Обычно Next добавляет **/*.d.ts автоматически, но если нет — добавь:

{
  "compilerOptions": {
    "strict": true,
    "jsx": "preserve",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "allowJs": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts"],
  "exclude": ["node_modules"]
}

🔹 Файл globals.css

Убедись, что у тебя реально есть файл:

/app/globals.css

или

/src/app/globals.css

👉 Если он лежит в src/app/, то импорт должен быть import "../globals.css"; из layout.tsx.


🔹 Быстрый фикс (если не хочешь возиться с типами)

Можно добавить "skipLibCheck": true в tsconfig.json (часто включено по умолчанию у Next). Это отключает проверку типов у импортов .css.


Админка


🔹 Вариант 1. Встроенная админка в Next

Ты просто делаешь отдельный роут /admin внутри app/ или pages/, и это будет админка.

Структура:

app/
 ├─ admin/
 │   ├─ layout.tsx   # общий layout для админки
 │   └─ page.tsx     # главная страница админки

Пример:

// app/admin/layout.tsx
"use client";

import { useSession } from "next-auth/react"; // если будешь использовать авторизацию
import type { ReactNode } from "react";

export default function AdminLayout({ children }: { children: ReactNode }) {
  const { data: session } = useSession();

  if (!session) return <p>Access denied</p>; // защита

  return (
    <div className="admin-layout">
      <aside>Sidebar (меню админки)</aside>
      <main>{children}</main>
    </div>
  );
}
// app/admin/page.tsx
export default function AdminPage() {
  return <h1>Админка — Dashboard</h1>;
}

👉 У тебя будет полноценная защищённая зона /admin.


🔹 Вариант 2. Админка через headless CMS

Если тебе нужно редактировать контент (тексты, картинки, товары и т.д.), а не код руками:

  • Strapi — поднимешь бэкенд с админкой и API (GraphQL/REST).
  • Sanity — встроенная админка в браузере + API.
  • KeystoneJS — Node.js CMS с красивой админкой.

Тогда Next просто тянет данные по API:

const res = await fetch("http://localhost:1337/api/posts");
const posts = await res.json();

🔹 Вариант 3. React Admin (готовая библиотека)

React Admin можно встроить внутрь /admin роутера. Он даёт таблицы, CRUD, фильтры.

Пример:

// app/admin/page.tsx
"use client";

import { Admin, Resource, ListGuesser } from "react-admin";
import simpleRestProvider from "ra-data-simple-rest";

export default function AdminPage() {
  return (
    <Admin dataProvider={simpleRestProvider("/api")}>
      <Resource name="users" list={ListGuesser} />
      <Resource name="posts" list={ListGuesser} />
    </Admin>
  );
}

🔐 Важное: защита админки

  • NextAuth.js — лучшее решение для авторизации (Google, GitHub, JWT, Email).
  • Можно добавить middleware.ts, чтобы перенаправлять неавторизованных:
// middleware.ts
import { getToken } from "next-auth/jwt";
import { NextResponse } from "next/server";

export async function middleware(req: Request) {
  const token = await getToken({ req });
  const url = new URL(req.url);

  if (url.pathname.startsWith("/admin") && !token) {
    return NextResponse.redirect(new URL("/api/auth/signin", req.url));
  }
  return NextResponse.next();
}

Backend


🔹 Где писать бэкенд в Next.js?

1. App Router (новый, app/)

API создаётся в папке app/api/*/route.ts. Каждый route.ts = endpoint.

Пример:

app/
 ├─ api/
 │   ├─ users/
 │   │   └─ route.ts
 │   └─ posts/
 │       └─ route.ts
// app/api/users/route.ts
import { NextResponse } from "next/server";

export async function GET() {
  return NextResponse.json([{ id: 1, name: "Alice" }]);
}

export async function POST(req: Request) {
  const body = await req.json();
  return NextResponse.json({ message: "User created", user: body });
}

👉 Теперь у тебя есть API по адресу /api/users.


2. Pages Router (старый, pages/)

Если у тебя pages/, то API делается в pages/api/*.

// pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from "next";

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === "GET") {
    res.status(200).json([{ id: 1, name: "Bob" }]);
  } else if (req.method === "POST") {
    res.status(201).json({ message: "User created" });
  }
}

🔹 Можно ли работать с БД?

Да, без проблем:

  • Prisma (Postgres, MySQL, SQLite, MongoDB)
  • Mongoose (MongoDB)
  • node-postgres (pg)
  • Drizzle ORM (лёгкий ORM, очень модный сейчас)

Пример (Prisma):

// app/api/users/route.ts
import { PrismaClient } from "@prisma/client";
import { NextResponse } from "next/server";

const prisma = new PrismaClient();

export async function GET() {
  const users = await prisma.user.findMany();
  return NextResponse.json(users);
}

export async function POST(req: Request) {
  const data = await req.json();
  const newUser = await prisma.user.create({ data });
  return NextResponse.json(newUser);
}

🔹 Плюсы подхода «бэкенд в том же репо»

✅ Один кодовый репозиторий — легче поддерживать. ✅ Нет CORS — фронт и бэк в одном домене. ✅ Быстрое прототипирование (API готов сразу). ✅ Можно деплоить на Vercel / Netlify без отдельного сервера.


🔹 Минусы

⚠️ Если бэкенд очень тяжёлый (огромное API, очереди, WebSocket-ы), может быть лучше вынести его в отдельный сервис. ⚠️ Лимиты serverless (например, у Vercel есть ограничения на время выполнения функций).


БД


🔹 Вариант 1. Мок-данные прямо в коде

Ты уже так делал — lorem ipsum, 12345, массивы.

const users = [
  { id: 1, name: "Alice", email: "alice@test.com" },
  { id: 2, name: "Bob", email: "bob@test.com" },
];

✅ Быстро ✅ Ничего не ломается ❌ Нет ощущения “реальной БД” ❌ Придётся руками править данные


🔹 Вариант 2. Фейковая БД (SQLite/Postgres) через ORM

Можно подключить Prisma и завести SQLite-файл (db.sqlite) прямо в проекте.

Пример:

  1. Установил Prisma:
npm install prisma @prisma/client
npx prisma init --datasource-provider sqlite
  1. prisma/schema.prisma:
model User {
  id    Int     @id @default(autoincrement())
  name  String
  email String  @unique
}
  1. Прогнал миграцию:
npx prisma migrate dev --name init
  1. Теперь можешь использовать в API:
// app/api/users/route.ts
import { PrismaClient } from "@prisma/client";
import { NextResponse } from "next/server";

const prisma = new PrismaClient();

export async function GET() {
  const users = await prisma.user.findMany();
  return NextResponse.json(users);
}

✅ Реалистично (запросы, CRUD, типы) ✅ Полезно для прокачки навыков ❌ Чуть дольше настраивать, чем мок-данные


🔹 Вариант 3. Фейковый API без БД

Можно подключить JSON Server. Ты заводишь db.json → и он поднимает фейковый REST API.

{
  "users": [
    { "id": 1, "name": "Alice" },
    { "id": 2, "name": "Bob" }
  ]
}

Запуск:

npx json-server --watch db.json --port 4000

Теперь в Next можно делать:

const res = await fetch("http://localhost:4000/users");
const users = await res.json();

✅ Очень просто ✅ Похоже на “реальный” бэкенд ❌ Отдельный процесс, лишний для MVP


Магазин


🔹 Архитектура магазина в Next.js

1. Структура страниц

app/
 ├─ shop/           → список товаров
 │   ├─ page.tsx
 │   └─ [id]/page.tsx   → страница товара
 ├─ cart/page.tsx   → корзина
 └─ checkout/page.tsx → оформление заказа

2. Данные о товарах

Варианты:

  • 🟢 На старте: JSON / мок-данные (типа products.json).
  • 🟢 Для практики: SQLite + Prisma (Product модель).
  • 🟡 В будущем: подключить headless-CMS (Strapi, Sanity) или вынести в API.

Пример модели Prisma:

model Product {
  id          Int      @id @default(autoincrement())
  name        String
  description String
  price       Float
  imageUrl    String
  createdAt   DateTime @default(now())
}

3. API для магазина

app/api/products/route.ts      → список товаров
app/api/cart/route.ts          → корзина
app/api/orders/route.ts        → заказы

Пример app/api/products/route.ts:

import { NextResponse } from "next/server";
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export async function GET() {
  const products = await prisma.product.findMany();
  return NextResponse.json(products);
}

4. UI (React + TSX)

  • /shop → карточки товаров (с фото, ценой, кнопкой “в корзину”).
  • /cart → список добавленных товаров.
  • /checkout → форма для оплаты.

🔹 Оплата (главный момент)

У тебя 3 пути:

🟢 Stripe (самый популярный)

  • Платежи по карте, Apple Pay, Google Pay.
  • Есть Next.js SDK (@stripe/stripe-js).
  • Для цифровых товаров (музыка, пресеты) идеально.

Пример (очень упрощённый):

// app/api/checkout/route.ts
import { NextResponse } from "next/server";
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(req: Request) {
  const { items } = await req.json();
  const session = await stripe.checkout.sessions.create({
    payment_method_types: ["card"],
    line_items: items,
    mode: "payment",
    success_url: "http://localhost:3000/success",
    cancel_url: "http://localhost:3000/cancel",
  });
  return NextResponse.json({ url: session.url });
}

🟡 PayPal

  • Простой вариант для фрилансеров/артистов.
  • Есть кнопки оплаты → вставляешь в checkout.

🟠 Web3 (позже)

  • Подключение Metamask, USDC/ETH платежей.
  • Для твоей идеи с децентрализацией это 🔮, но лучше оставить на потом.

🔹 Минимальный flow магазина

  1. /shop → список товаров.
  2. /cart → клиентский стейт (useState, Redux или Zustand).
  3. /checkout → создаём Stripe-сессию, редирект на оплату.
  4. /success → пользователь возвращается после оплаты, создаём заказ в базе.