TypeScript
// Какой-нибудь СЕРВЕРНЫЙ модуль.
import { cookies } from 'next/headers'
const cookieStore = cookies() // cookieStore: ReadonlyRequestCookies
Нормально
Игорь Быстрицкий
Что нового в Next 14
Next 14, выпущенный 26 октября 2023 года, ускоряет время сборки в 2 раза и добавляет несколько новых функций.
Приятные шрифт от Vercel - Geist Sans и Geist Mono
"Geist действительно олицетворяет дух программирования и дизайна в творческом сообществе Vercel" - заявляют в Vercel.
Типизация страницы в Next
Используй TypeScript и Next вместе, чтобы создать кастомный тип TNextPage, с помощью которого можно с легкостью указать типы params и searchParams на любой странице.
Шрифты в Next и Tailwind
Расскажу о любимом способе использовать шрифты на проектах с Next и Tailwind
С печеньями в Next работать очень просто, достаточно лишь держать в голове некоторые прихоти самого Next. Для работы с печеньями существует серверная функция cookies
, которая позволяет управлять ими, как угодно:
// Какой-нибудь СЕРВЕРНЫЙ модуль.
import { cookies } from 'next/headers'
const cookieStore = cookies() // cookieStore: ReadonlyRequestCookies
Теперь можно и прочитать печенье с помощью функции get
:
// Так можно прочитать печенье, которое гласит, что юзер впервые на сайте.
const firstTimeOnSite = cookieStore.get('first-time-on-site')?.value // firstTimeOnSite: string | undefined
if (JSON.parse(firstTimeOnSite) === false) {
console.log('Привет, давно не виделись! 😉')
}
Или создать печенье, используя set
:
// Печенье будет висеть в браузере целый год (насколько я знаю, это максимальное время).
cookies().set('first-time-on-site', 1, { maxAge: 60 * 60 * 24 * 365 })
// Теперь это печенье перезапишется со значением 0, но удалится в конце сессии вообще.
cookies().set('first-time-on-site', 0)
Next не предполагает, что вы будете использовать печенья на клиенте. Любые функции, которые взаимодействуют с печеньями должны принадлежать коду сервера, а не клиента, в этот список входят:
Свои функции по работе с печеньями можно сделать серверными действиями, объявив их в модуле с директивой "use server"
, например в файле src/actions.ts
. Также Next требует, чтобы серверные действия были асинхронными, вне зависимости от их содержимого.
Ниже серверное действие, которое создает печенье "скрыть информационный баннер":
'use server'
import { revalidatePath } from 'next/cache'
import { cookies } from 'next/headers'
export async function hideInfoBanner() {
cookies().set('hide-info-banner', 1, { maxAge: 60 * 60 * 24 * 365 })
revalidatePath('/')
}
Скорее всего, новенькая функция hideInfoBanner
будет вызываться по нажатию кнопки "закрыть" или типа того. Клик будет на клиенте, но как тогда вызывать серверную функцию? Для этого необходимо использовать элемент form
и его аттрибут action
:
import { getCookie, hideInfoBanner } from '../actions'
export default async function Home() {
const ifHideInfoBanner = await getCookie('hide-info-banner')
return (
<main>
{!ifHideInfoBanner && (
<article>
<div>
<h1>Впервые на сайте?</h1>
<h2>
Узнайте больше о проекте на <Link href={'/info'}>этой странице</Link>.
</h2>
</div>
<form action={hideInfoBanner}>
{/** Нажимая на кнопку, форма вызовет серверную функцию. */}
<button type='submit'>
<TbX />
</button>
</form>
</article>
)}
</main>
)
}
Допустим, вы хотите создать печенье для хранения текущего языка, таки у вас дорогой мультиязычный сайт, поддерживающий 3 языка, включая русский, английский и даже китайский:
cookies().set('lang', 'ru')
cookies().set('lang', 'en')
cookies().set('lang', 'ch')
Все здорово, но никто не мешает вам установить печенья языка на французский язык, вдруг вы случайно случайно подумали, что он поддерживается:
cookies().set('lang', 'fr')
Во избежание такой ошибки нам нужно создать вспомогательную функцию с нужными типами:
function setLangCookie(lang: 'ru' | 'en' | 'ch') {
cookies().set('lang', lang, { maxAge: 60 * 60 * 24 * 365 })
}
setLangCookie('ru') // Можно.
setLangCookie('fr') // Не можно.
Но вот у нас на сайте теперь появилась темная тема, создавать для нее идентичную функцию будет странно. Лучше создать функцию общего назначения, которая бы позволила нам взаимодействовать с любым печеньем нашего приложения, а типы печений тогда прописать в другом месте:
import { ResponseCookie } from 'next/dist/compiled/@edge-runtime/cookies'
import { cookies } from 'next/headers'
type TCookies = {
lang: 'ru' | 'en' | 'ch' | undefined // Печенья может и не быть.
dark: true | undefined // Темная тема либо включена, либо печенья не будет вообще и включится белая.
}
export function setCookie<N extends keyof TCookies>(name: N, value: NonNullable<TCookies[N]>, options?: Partial<ResponseCookie>) {
store.set(name, JSON.stringify(value), options)
}
setCookie
позволяет нам выбрать лишь lang
или dark
а тип 2-го параметра соответствует тому, что мы указали в TCookies
:
setCookie('lang', 'ru')
setCookie('lang', 'fr') // Нельзя.
setCookie('theme', true)
setCookie('theme', undefined) // Нельзя. Лучше печенье эксплицитно удалить.
Полный набор функций для работы с печеньями находится в серверном модуле actions.ts
(название может быть любым) и выглядит так:
'use server'
import { ResponseCookie } from 'next/dist/compiled/@edge-runtime/cookies'
import { cookies } from 'next/headers'
// Объявление печений.
type TCookies = {
lang: 'ru' | 'en' | 'ch' | undefined
dark: true | undefined
'hide-info-banner': true | undefined
}
// Создание или перезапись печенья.
export async function setCookie<N extends keyof TCookies>(name: N, value: NonNullable<TCookies[N]>, options?: Partial<ResponseCookie>) {
cookies().set(name, JSON.stringify(value), options)
}
// Чтение печенья.
export async function getCookie<N extends keyof TCookies>(name: N) {
const value = cookies().get(name)?.value
if (!value) return
return JSON.parse(value) as TCookies[N]
}
// Проверка наличия печенья.
export async function hasCookie<N extends keyof TCookies>(name: N) {
return !!cookies().get(name)?.value
}
// Удаление печенья.
export async function deleteCookie<N extends keyof TCookies>(name: N) {
return !!cookies().delete(name)
}
// Здесь же желательно сразу объявить функции, которые будут использоваться в формах клиентских компонентов, например функция hideInfoBanner.
export async function hideInfoBanner() {
setCookie('hide-info-banner', true, { maxAge: 60 * 60 * 24 * 365 })
revalidatePath('/')
}