Что такое Next.js?
NextJS - это фреймворк, построенный на базе React, который генерирует код заранее на сервере, чтобы отобразить его пользователю. Так же он правит сео-оптимизацию.
Преимущества
- Тег
<Img>
, в который некст принимает изображение и на выходе генерирует оптимизированное изображение - Удобный роутинг с хранением всех страниц в одной папке и лёгким перемещением через тег
<Link>
- SSR и SSG генерации
- Так же можно поднять серверное API внутри маленького проекта
- Контроль мета-тегов через компонент
<HEAD>
Установка проекта
npx create-next-app@latest
Разбор структуры
pages
- папка с API проекта и его страницамиpublic
- папка с ресурсами проектаstyles
- стили проекта
Проект, который будем делать
Роутинг
Первым делом можно отметить возможность создания динамических страниц. В страницу подкладывается переменная для генерации её под определённый подставленный объект.
Шаблон записывается в []
скобочках
Пропсы компонент App
получает из кастомного документа _document.jsx
, который можно создать для определения метаданных наших страниц
_app.js
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />
}
Компонент Head
Для создания Head
под каждую страницу, можно выделить компонент, который будет сохранять в себе метаданные
Примерно в этом месте мы можем реализовать все сторонние компоненты и дополнения для системы
Это сам компонент метаданных в приложении
meta.jsx
import Head from 'next/head';
const Meta = ({ title, description }) => {
return (
<Head>
<title>{title}</title>
<meta name='description' content={description} />
</Head>
);
};
export default Meta;
И так применяем в приложении
export default function Home() {
return (
<>
<Meta title='Главная' description='Описание страницы' />
{ /// CODE ... }
</>
);
}
Кастомный document.js
Создаём в качестве страниц наше представление документа. Оно показывает, в каком порядке будут располагаться элементы на странице
Здесь сначала располагается начальная структура документа, а уже затем можно добавлять остальные элементы страницы (те же шрифты)
_document.js
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html lang='en'>
<Head>
<link rel='preconnect' href='https://fonts.googleapis.com' />
<link rel='preconnect' href='https://fonts.gstatic.com' crossOrigin="true" />
<link
href='https://fonts.googleapis.com/css2?family=Sevillana&display=swap'
rel='stylesheet'
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
И так же поменяем шрифт на странице в глобальных стилях
:root {
--max-width: 1100px;
--border-radius: 12px;
--font-mono: Sevillana, monospace;
/* CODE ... */
}
Установка SCSS
Просто устанавливаем модуль сасс
npm i sass
Переименовываем файлы в scss
И далее меняем импорты
import styles from '../styles/Home.module.scss';
Создание своего API на next.js
data.js
export const cards = [
{
_id: "first-card",
balance: 44_834_342,
number: "4568 6456 7875 6567",
color: "Black",
},
{
_id: "second-card",
balance: 44_834,
number: "4567 3456 9875 4567",
color: "0095FF",
}
];
Это общий запрос, который выполняется при запросе без определённого id
карточки
index.js
import {cards} from "../../../app/data";
export default function handler(req, res) {
res.status(200).json(cards);
}
Сейчас мы можем себе позволить проверить получаемый id
карточки
[id].js
import {cards} from "../../../app/data";
export default function handler(req, res) {
console.log(req.query.id)
res.status(200).json(cards);
}
В ответе мы получаем:
// Ссылка на запрос
http://localhost:3000/api/cards
// ответ от сервера
[{"_id":"first-card","balance":44834342,"number":"4568 6456 7875 6567","color":"Black"},{"_id":"second-card","balance":44834,"number":"4567 3456 9875 4567","color":"0095FF"}]
В логе мы получаем:
И вот так будет выглядеть выполняемый запрос при вводе карточки. Он будет возвращать только данные по карточке
[id].js
import {cards} from "../../../app/data";
export default function handler(req, res) {
res.status(200).json(cards.find(card => card._id === req.query.id));
}
getServerSideProps
Мы можем получать через getServerSideProps
свойства с сервера, чтобы применять их у себя на странице. Это не самый эффективный вариант для работы приложения, так как на каждый запрос пользователя будет напрягаться сервер.
Более актуальным вариантом взаимодействия с сервером является getStaticProps
export default function Home({cards}) {
console.log(cards) // выводим карточки в консоль
return (
<>
{ /// CODE ... }
</>
);
}
// получение пропсов с сервера
export const getServerSideProps = async () => {
const response = await fetch('http://localhost:3000/api/cards');
const cards = await response.json();
return {
props: {
cards
}
};
}
getStaticProps
Уже getStaticProps
подгружает данные для клиента при загрузке страницы ровно один раз и выдаёт ему уже сформированные данные.
getStaticPaths
Вкупе с методом выше так же используют getStaticPaths
, который подгружает пути данных
Вот так выглядит структура:
Первым делом заменим в индексе getServerSideProps
на getStaticProps
и добавим параметр revalidate
, чтобы производить валидацю данных
index.js
export const getStaticProps = async () => {
const response = await fetch('http://localhost:3000/api/cards');
const cards = await response.json();
return {
props: {
cards
},
revalidate: 10
};
}
И теперь создадим страницу, которая будет генерироваться на основании переданных нами данных.
То есть по запросу id
, который будет возвращать определённую карту, будет выводиться страница, которая выведет данную карту
[id].jsx
import React from 'react';
const Card = ({card}) => {
return (
<div>
{card.number}
</div>
);
};
export const getStaticPaths = async () => {
const response = await fetch('http://localhost:3000/api/cards');
const cards = await response.json();
const paths = cards.map(c => ({params: {id: c._id}}))
return {paths, fallback: "blocking"}
}
export const getStaticProps = async ({params}) => {
const response = await fetch(`http://localhost:3000/api/cards/${params.id}`);
const card = await response.json();
return {
props: {
card
},
revalidate: 10
};
}
export default Card;
И теперь по запросу http://localhost:3000/card/first-card
минуя api
мы можем получить интересующие нас данные
Tailwind CSS
npm i -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./pages/**/*.{js,ts,jsx,tsx}', './app/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
}
globals.scss
@tailwind base;
@tailwind components;
@tailwind utilities;
Практика Next.js
Так выглядит структура:
Это компонент отдельной карточки. Тут используются:
- Стили как через
className
, так и черезstyle
- Тег некста
<Link>
- Тег некста
<Image>
app > cards > CardItem.jsx
import React from 'react';
import Image from 'next/image';
import Link from 'next/link';
const CardItem = ({ card }) => {
return (
// тут находится вся наша карточка
<div
className="w-5/6 mx-auto rounded-xl p-5 mb-3 text-white overflow-hidden bg-sky-700"
style={{
background: card.color,
}}
>
{/* а тут всю карточку делаем ссылкой */}
{/* тут используется тег некста - Link */}
<Link href={`/card/${card.id}`}>
{/* Это уже тег некста - Image, который позволяет нам вставить нормально изображение. Так же некст сам преобразует нужным образом изображение */}
<Image
src={
'https://www.seekpng.com/png/detail/136-1366968_mastercard-download-png-mastercard-credit-card-png.png'
}
alt="Mastercard Download Png - Mastercard Credit Card Png@seekpng.com"
width={40}
height={30}
/>
<div
className="mt-6 mb-1 opacity-50"
style={{
fontSize: 11,
}}
>
Current Balance
</div>
<div>
{/* Эта настройка преобразует полученное число в денежную валюту - рубли */}
{card.balance.toLocaleString('ru-Ru', {
currency: 'RUB',
style: 'currency',
})}
</div>
<div className="mt-6 text-xs">{card.number}</div>
</Link>
</div>
);
};
export default CardItem;
Тут уже мы будем выводить страницу по переходу на отдельную определённую карточку по клику
pages > card > [id].jsx
import Link from 'next/link';
import React from 'react';
import CardItem from '../../app/cards/CardItem';
const Card = ({ card }) => {
return (
<div>
<Meta title={`Карточка ${card._id}`} description="" />
<main className="w-1/2 mx-auto mt-10">
<CardItem card={card} />
</main>
<Link href="/">
<p>Back home</p>
</Link>
</div>
);
};
export const getStaticPaths = async () => {
const response = await fetch('http://localhost:3000/api/cards');
const cards = await response.json();
const paths = cards.map((c) => ({ params: { id: c._id } }));
return { paths, fallback: 'blocking' };
};
export const getStaticProps = async ({ params }) => {
const response = await fetch(`http://localhost:3000/api/cards/${params.id}`);
const card = await response.json();
return {
props: {
card,
},
revalidate: 10,
};
};
export default Card;
Чтобы мы могли загружать с помощью тега <Image>
изображения со сторонних ресурсов, нужно добавить их в домены внутри конфига
Чтобы увидеть изменения на сайте при изменениях в конфиге, нужно перезапустить сборку в консоли
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
images: {
domains: ['www.seekpng.com'],
},
}
module.exports = nextConfig
Тут мы выводим все наши карты на страницу из массива переданных карт в компонент Home
pages > index.js
export default function Home({ cards }) {
return (
<>
<Meta title="Главная" description="Описание страницы" />
<main className="w-1/2 mx-auto mt-10">
{cards.map((card) => (
<CardItem key={card._id} card={card} />
))}
</main>
</>
);
}
export const getStaticProps = async () => {
const response = await fetch('http://localhost:3000/api/cards');
const cards = await response.json();
return {
props: {
cards,
},
revalidate: 10,
};
};
И вот так будет выглядеть итоговая страница:
- Выводятся все карточки, которые добавим в
data.js
- Переход по карточкам работать не будет (для этого нужно пилить отдельно бэк)