{"id":345128,"date":"2023-02-08T15:00:57","date_gmt":"2023-02-08T15:00:57","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=345128"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2023-02-08T15:00:57","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=345128","title":{"rendered":"<span>\u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043a\u043b\u0438\u0435\u043d\u0442-\u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Next.js \u0438 TypeScript. \u0427\u0430\u0441\u0442\u044c 2. \u0420\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0430<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/by\/f9\/qu\/byf9qu-qkjvc3cpega2osikmrcq.png\" data-src=\"https:\/\/habrastorage.org\/webt\/by\/f9\/qu\/byf9qu-qkjvc3cpega2osikmrcq.png\"\/>  <\/p>\n<p>  \u041f\u0440\u0438\u0432\u0435\u0442, \u0434\u0440\u0443\u0437\u044c\u044f!<\/p>\n<p>  <\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0435\u0440\u0438\u0438 \u0438\u0437 2 \u0441\u0442\u0430\u0442\u0435\u0439-\u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u043e\u0432 \u043c\u044b \u0441 \u0432\u0430\u043c\u0438 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u043c \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442-\u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0435 (\u0444\u0443\u043b\u043b\u0441\u0442\u0435\u043a \u2014 fullstack) \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/nextjs.org\/\">Next.js<\/a> \u0438 <a href=\"https:\/\/www.typescriptlang.org\/\">TypeScript<\/a>.<\/p>\n<p>  <\/p>\n<ul>\n<li><a href=\"https:\/\/my-js.org\/docs\/guide\/nextjs\">\u0420\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e \u043f\u043e Next.js<\/a>.<\/li>\n<li><a href=\"https:\/\/my-js.org\/docs\/guide\/ts\">\u041a\u0430\u0440\u043c\u0430\u043d\u043d\u0430\u044f \u043a\u043d\u0438\u0433\u0430 \u043f\u043e TypeScript<\/a>.<\/li>\n<\/ul>\n<p>  <\/p>\n<ol>\n<li>\u041d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u0431\u043e\u0439 \u0431\u043b\u043e\u0433 \u2014 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u0443\u044e \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0443 \u0434\u043b\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438, \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u043e\u0432.<\/li>\n<li>\u041c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 <a href=\"https:\/\/jwt.io\/\">JSON Web Tokens<\/a> \u0438 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Cookies\">HTTP-\u043a\u0443\u043a\u0438<\/a>.<\/li>\n<li>\u0414\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0438 \u043f\u043e\u0441\u0442\u043e\u0432 \u0431\u0443\u0434\u0443\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0441\u044f \u0432 \u0440\u0435\u043b\u044f\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 <a href=\"https:\/\/www.sqlite.org\/\">SQLite<\/a>.<\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u0412 <a href=\"https:\/\/habr.com\/ru\/company\/timeweb\/blog\/712294\/\">\u043f\u0435\u0440\u0432\u043e\u043c \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0435<\/a> \u043c\u044b \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b\u0438 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043b\u0438 \u043f\u0440\u043e\u0435\u043a\u0442, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0440\u043e\u0443\u0442\u043e\u0432 (API Routes), \u0432\u043e \u0432\u0442\u043e\u0440\u043e\u043c \u2014 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0440\u0430\u0431\u043e\u0442\u043e\u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>  <\/p>\n<p><em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em>: \u0434\u0430\u043d\u043d\u044b\u0439 \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d \u043d\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u043c\u0435\u044e\u0442 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u043f\u044b\u0442 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 <a href=\"https:\/\/ru.reactjs.org\/\">React<\/a> \u0438 <a href=\"https:\/\/nodejs.org\/\">Node.js<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0442\u0435\u0445, \u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0434, \u0432\u043e\u0442 <a href=\"https:\/\/github.com\/harryheman\/Blog-Posts\/tree\/master\/fullstack-next-ts-app\">\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0418\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e? \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u043e\u0448\u0443 \u043f\u043e\u0434 \u043a\u0430\u0442.<\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<h2 id=\"nastroyka-proekta\">\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h2>\n<p>  <\/p>\n<h3 id=\"why-did-you-render\">Why Did You Render<\/h3>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/welldone-software\/why-did-you-render\">Why Did You Render<\/a> \u2014 \u0443\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 React-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0430\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u0440\u0438\u0447\u0438\u043d\u0443 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430. \u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u0443 \u0443\u0442\u0438\u043b\u0438\u0442\u0443 \u0432 Next.js-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c 2 \u0432\u0435\u0449\u0438:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u0440\u0435\u0441\u0435\u0442 (preset) \u0442\u0440\u0430\u043d\u0441\u043f\u0438\u043b\u044f\u0442\u043e\u0440\u0430 <a href=\"https:\/\/babeljs.io\/\">Babel<\/a>;<\/li>\n<li>\u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0443\u0442\u0438\u043b\u0438\u0442\u0443 \u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0435 \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u043f\u0440\u0435\u0441\u0435\u0442 Babel \u0432 \u0444\u0430\u0439\u043b\u0435 <code>babel.config.js<\/code> \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">module.exports = function (api) {   const isServer = api.caller((caller) => caller?.isServer)   const isCallerDevelopment = api.caller((caller) => caller?.isDev)    \/\/ \u043f\u0440\u0435\u0441\u0435\u0442\u044b   const presets = [     [       'next\/babel',       {         'preset-react': {           runtime: 'automatic',           importSource:             \/\/ \u043a\u043e\u0434 wdyr \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435             \/\/ \u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438             !isServer &amp;&amp; isCallerDevelopment               ? '@welldone-software\/why-did-you-render'               : 'react'         }       }     ]   ]    return { presets } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c WDYR \u0432 \u0444\u0430\u0439\u043b\u0435 <code>utils\/wdyr.ts<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import React from 'react'  \/\/ \u043a\u043e\u0434 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \/\/ \u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 if (process.env.NODE_ENV === 'development' &amp;&amp; typeof document !== 'undefined') {   const whyDidYouRender = require('@welldone-software\/why-did-you-render')   whyDidYouRender(React, {     trackAllPureComponents: true   }) }  export {}<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c WDYR \u0432 \u0444\u0430\u0439\u043b\u0435 <code>_app.tsx<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import '@\/utils\/wdyr'<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u0432 \u0444\u0430\u0439\u043b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0441\u0442\u0440\u043e\u0447\u043a\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">SomeComponent.whyDidYouRender = true<\/code><\/pre>\n<p>  <\/p>\n<h3 id=\"material-ui\">Material UI<\/h3>\n<p>  <\/p>\n<p><a href=\"https:\/\/mui.com\/\">Material UI<\/a> \u2014 \u0441\u0430\u043c\u0430\u044f \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u0430\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 React. \u0414\u043b\u044f \u0435\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432 Next.js-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c 2 \u0432\u0435\u0449\u0438:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043b\u0430\u0433\u0438\u043d (plugin) Babel;<\/li>\n<li>\u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043a\u044d\u0448 <a href=\"https:\/\/emotion.sh\/docs\/introduction\">Emotion<\/a> \u2014 \u0440\u0435\u0448\u0435\u043d\u0438\u044f CSS-\u0432-JS, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f MUI \u0434\u043b\u044f \u0441\u0442\u0438\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u043f\u043b\u0430\u0433\u0438\u043d Babel \u0432 \u0444\u0430\u0439\u043b\u0435 <code>babel.config.js<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">module.exports = function (api) {   \/\/ \u043f\u0440\u0435\u0441\u0435\u0442\u044b   \/\/ ...    \/\/ \u043f\u043b\u0430\u0433\u0438\u043d\u044b   const plugins = [     [       'babel-plugin-import',       {         libraryName: '@mui\/material',         libraryDirectory: '',         camel2DashComponentName: false       },       'core'     ]   ]    return { presets, plugins } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0447\u0435\u0433\u043e \u043d\u0443\u0436\u0435\u043d \u044d\u0442\u043e\u0442 \u043f\u043b\u0430\u0433\u0438\u043d? \u0414\u043b\u044f \u0443\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u0438\u044f \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0439 \u0441\u0431\u043e\u0440\u043a\u0438. \u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u043f\u0440\u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 MUI \u043f\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044e, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { Button } from '@mui\/material'<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412 \u0441\u0431\u043e\u0440\u043a\u0443 \u043f\u043e\u043f\u0430\u0434\u0435\u0442 \u0432\u0435\u0441\u044c \u043f\u0430\u043a\u0435\u0442 <code>@mui\/material<\/code>, \u0442.\u0435. \u0432\u0441\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b MUI \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u043e\u0442 \u0442\u043e\u0433\u043e, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u043e\u043d\u0438 \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0438\u043b\u0438 \u043d\u0435\u0442. <code>babel-plugin-import<\/code> \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u0442 \u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0438\u043c\u043f\u043e\u0440\u0442 \u0432 \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0439, \u0442.\u0435. \u043d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u043c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import Button from '@mui\/material\/Button'<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0432 \u0441\u0431\u043e\u0440\u043a\u0443 \u043f\u043e\u043f\u0430\u0434\u0430\u044e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u044d\u0448\u0430 Emotion \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0434\u043b\u044f \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u0432\u0441\u043f\u044b\u0448\u043a\u0438 \u043d\u0435\u0441\u0442\u0438\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 (flash of unstyled content), \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043a\u043e\u0433\u0434\u0430 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u044e\u0442\u0441\u044f \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0435 \u0441\u0442\u0438\u043b\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0442\u043e\u043c \u0441\u0442\u0438\u043b\u0438 MUI, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0434\u043b\u044f \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043b\u0435\u0433\u043a\u043e\u0439 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0438 \u0441\u0442\u0438\u043b\u0435\u0439 MUI, \u0442.\u0435. \u043a\u0430\u0441\u0442\u043e\u043c\u0438\u0437\u0430\u0446\u0438\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 (<a href=\"https:\/\/github.com\/mui\/material-ui\/tree\/master\/examples\/nextjs\">\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0440\u0435\u0448\u0435\u043d\u0438\u044f<\/a>).<\/p>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0443\u0442\u0438\u043b\u0438\u0442\u0443 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u044d\u0448\u0430 Emotion \u0432 \u0444\u0430\u0439\u043b\u0435 <code>utils\/createEmotionCache.ts<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import createCache from '@emotion\/cache'  \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u0442\u0435\u0433 `meta` \u0441 `name=\"emotion-insertion-point\"` \u0432 \u043d\u0430\u0447\u0430\u043b\u0435  &lt;head>. \/\/ \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0441\u0442\u0438\u043b\u0438 MUI \u0432 \u043f\u0435\u0440\u0432\u043e\u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435. \/\/ \u042d\u0442\u043e \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c \u043b\u0435\u0433\u043a\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0441\u0442\u0438\u043b\u0438 MUI, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u043e\u0434\u0443\u043b\u0435\u0439 CSS. export default function createEmotionCache() {   let insertionPoint    if (typeof document !== 'undefined') {     const emotionInsertionPoint = document.querySelector&lt;HTMLMetaElement>(       'meta[name=\"emotion-insertion-point\"]'     )     insertionPoint = emotionInsertionPoint ?? undefined   }    return createCache({ key: 'mui-style', insertionPoint }) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u044d\u0448 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043a\u0430\u043a \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435, \u0442\u0430\u043a \u0438 \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435. \u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0432 \u0444\u0430\u0439\u043b\u0435 <code>_document.tsx<\/code> (\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u044d\u0448\u0430 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import createEmotionCache from '@\/utils\/createEmotionCache' import createEmotionServer from '@emotion\/server\/create-instance' import Document, {   DocumentContext,   Head,   Html,   Main,   NextScript } from 'next\/document'  export default function MyDocument(props: any) {   return (     &lt;Html lang='en'>       &lt;Head>         &lt;link rel='icon' href='data:.' \/>         {\/* \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u043c \u0448\u0440\u0438\u0444\u0442\u043e\u043c MUI \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f Roboto, \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Montserrat *\/}         &lt;link rel='preconnect' href='https:\/\/fonts.googleapis.com' \/>         &lt;link rel='preconnect' href='https:\/\/fonts.gstatic.com' \/>         &lt;link           href='https:\/\/fonts.googleapis.com\/css2?family=Montserrat:wght@300;400;500&amp;display=swap'           rel='stylesheet'         \/>         {\/* ! *\/}         &lt;meta name='emotion-insertion-point' content='' \/>         {props.emotionStyleTags}       &lt;\/Head>       &lt;body>         &lt;Main \/>         &lt;NextScript \/>       &lt;\/body>     &lt;\/Html>   ) }  \/\/ `getInitialProps` \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0438\u0442 `_document` (\u0430 \u043d\u0435 `_app`), \/\/ \u044d\u0442\u043e \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e \u0441 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0435\u0439 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 (SSG). MyDocument.getInitialProps = async (docContext: DocumentContext) => {   \/\/ \u041f\u043e\u0440\u044f\u0434\u043e\u043a \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f   \/\/   \/\/ \u041d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435:   \/\/ 1. app.getInitialProps   \/\/ 2. page.getInitialProps   \/\/ 3. document.getInitialProps   \/\/ 4. app.render   \/\/ 5. page.render   \/\/ 6. document.render   \/\/   \/\/ \u041d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0448\u0438\u0431\u043a\u0438:   \/\/ 1. document.getInitialProps   \/\/ 2. app.render   \/\/ 3. page.render   \/\/ 4. document.render   \/\/   \/\/ \u041d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435:   \/\/ 1. app.getInitialProps   \/\/ 2. page.getInitialProps   \/\/ 3. app.render   \/\/ 4. page.render    const originalRenderPage = docContext.renderPage    \/\/ \u041a\u044d\u0448 Emotion \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u0432\u0441\u0435\u043c\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438 SSR \u0434\u043b\u044f \u043f\u043e\u0432\u044b\u0448\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438.   \/\/ \u041e\u0434\u043d\u0430\u043a\u043e \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043a \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u043c \u043f\u043e\u0431\u043e\u0447\u043d\u044b\u043c \u044d\u0444\u0444\u0435\u043a\u0442\u0430\u043c.   const cache = createEmotionCache()   const { extractCriticalToChunks } = createEmotionServer(cache)    docContext.renderPage = () =>     originalRenderPage({       enhanceApp: (App: any) =>         function EnhanceApp(props) {           return &lt;App emotionCache={cache} {...props} \/>         }     })    const docProps = await Document.getInitialProps(docContext)   \/\/ \u0412\u0430\u0436\u043d\u043e. \u042d\u0442\u043e \u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 Emotion \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u044c \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0439 HTML.   \/\/ \u0421\u043c. https:\/\/github.com\/mui\/material-ui\/issues\/26561#issuecomment-855286153   const emotionStyles = extractCriticalToChunks(docProps.html)   const emotionStyleTags = emotionStyles.styles.map((style) => (     &lt;style       data-emotion={`${style.key} ${style.ids.join(' ')}`}       key={style.key}       \/\/ eslint-disable-next-line react\/no-danger       dangerouslySetInnerHTML={{ __html: style.css }}     \/>   ))    return {     ...docProps,     emotionStyleTags   } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 \u0432 \u0444\u0430\u0439\u043b\u0435 <code>_app.tsx<\/code> (\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u044d\u0448\u0430 \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import '@\/utils\/wdyr' \/\/ \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0442\u0438\u043b\u0438 import '@\/global.scss' import createEmotionCache from '@\/utils\/createEmotionCache' import { CacheProvider, EmotionCache } from '@emotion\/react' \/\/ \u0441\u0431\u0440\u043e\u0441 CSS import CssBaseline from '@mui\/material\/CssBaseline' import { createTheme, ThemeProvider } from '@mui\/material\/styles' import type { AppProps } from 'next\/app'  \/\/ \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0442\u0435\u043c\u0443 MUI const theme = createTheme({   typography: {     fontFamily: 'Montserrat, sans-serif'   },   components: {     MuiListItem: {       styleOverrides: {         root: {           width: 'unset'         }       }     },     MuiListItemButton: {       styleOverrides: {         root: {           flexGrow: 'unset'         }       }     }   } })  \/\/ \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0438\u0439 \u043a\u044d\u0448 const clientSideEmotionCache = createEmotionCache()  export default function App({   Component,   pageProps,   emotionCache = clientSideEmotionCache }: AppProps &amp; { emotionCache?: EmotionCache }) {   return (     &lt;>       {\/* \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u043a\u044d\u0448\u0430 *\/}       &lt;CacheProvider value={emotionCache}>         {\/* \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0442\u0435\u043c\u044b *\/}         &lt;ThemeProvider theme={theme}>           {\/* \u0441\u0431\u0440\u043e\u0441 \u0441\u0442\u0438\u043b\u0435\u0439 *\/}           &lt;CssBaseline \/>           {\/* ... *\/}         &lt;\/ThemeProvider>       &lt;\/CacheProvider>     &lt;\/>   ) }<\/code><\/pre>\n<p>  <\/p>\n<h2 id=\"formirovanie-struktury-komponentov\">\u0424\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432<\/h2>\n<p>  <\/p>\n<p>\u0412 \u043d\u0430\u0448\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e &#171;\u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0445&#187; \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 (<a href=\"https:\/\/www.npmjs.com\/package\/react-toastify\">react-toastify<\/a>);<\/li>\n<li>\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0441\u043b\u0430\u0439\u0434\u0435\u0440\u0430 (<a href=\"https:\/\/swiperjs.com\/\">swiper<\/a>);<\/li>\n<li>\u043f\u0440\u0435\u0434\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u0435\u043b\u044c (<a href=\"https:\/\/www.npmjs.com\/package\/react-error-boundary\">react-error-boundary<\/a>).<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0423 \u043d\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u0449\u0438\u0439 \u043c\u0430\u043a\u0435\u0442 (layout) \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041c\u044b \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0435\u0433\u043e \u043f\u0440\u044f\u043c\u043e \u0432 <code>_app.tsx<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0430\u043d\u0438\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u043c\u0435\u0436\u0434\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/auto-animate.formkit.com\/\">@formkit\/auto-animate<\/a> (\u0434\u0430\u043d\u043d\u0443\u044e \u0443\u0442\u0438\u043b\u0438\u0442\u0443 \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0443 <a href=\"https:\/\/reactcommunity.org\/react-transition-group\/\">React Transition Group<\/a>).<\/p>\n<p>  <\/p>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u0438 \u0441\u0442\u0438\u043b\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ ... import ErrorFallback from '@\/components\/ErrorFallback' import Footer from '@\/components\/Footer' import CustomHead from '@\/components\/Head' import Header from '@\/components\/Header' import { useAutoAnimate } from '@formkit\/auto-animate\/react' import Box from '@mui\/material\/Box' import Container from '@mui\/material\/Container' import { ErrorBoundary } from 'react-error-boundary' import { ToastContainer } from 'react-toastify' import 'react-toastify\/dist\/ReactToastify.css' import 'swiper\/css' import 'swiper\/css\/navigation' import 'swiper\/css\/pagination'<\/code><\/pre>\n<p>  <\/p>\n<p>\u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">export default function App({   Component,   pageProps,   emotionCache = clientSideEmotionCache }: AppProps &amp; { emotionCache?: EmotionCache }) {   \/\/ \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u0430\u043d\u0438\u043c\u0438\u0440\u0443\u0435\u043c\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442   const [animationParent] = useAutoAnimate()    return (     &lt;>       &lt;CacheProvider value={emotionCache}>         &lt;ThemeProvider theme={theme}>           &lt;CssBaseline \/>           {\/* \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0432 `head` *\/}           &lt;CustomHead             title='Default Title'             description='This is default description'           \/>           {\/* \u043f\u0440\u0435\u0434\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u0435\u043b\u044c *\/}           &lt;ErrorBoundary             \/\/ \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442             FallbackComponent={ErrorFallback}             onReset={() => window.location.reload()}           >             &lt;Container               maxWidth='xl'               sx={{                 minHeight: '100vh',                 display: 'flex',                 flexDirection: 'column',                 overflow: 'hidden'               }}             >               &lt;Header \/>               &lt;Box component='main' flexGrow={1} ref={animationParent}>                 {\/* \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b *\/}                 &lt;Component {...pageProps} \/>               &lt;\/Box>               &lt;Footer \/>             &lt;\/Container>             {\/* \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 *\/}             &lt;ToastContainer autoClose={2000} hideProgressBar theme='colored' \/>           &lt;\/ErrorBoundary>         &lt;\/ThemeProvider>       &lt;\/CacheProvider>     &lt;\/>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0440\u0430\u0437\u0434\u0435\u043b <code>head<\/code> \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 (<code>components\/head.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import Head from 'next\/head'  type Props = {   title: string   description: string   children?: JSX.Element }  export default function CustomHead({ title, description, children }: Props) {   return (     &lt;Head>       &lt;title>{title}&lt;\/title>       &lt;meta name='description' content={description} \/>       {children}     &lt;\/Head>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0435\u0437\u0435\u0440\u0432\u043d\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 (<code>components\/ErrorFallback.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import {   Button,   Card,   CardActions,   CardContent,   CardHeader,   Typography } from '@mui\/material'  type Props = {   error: Error   resetErrorBoundary: (...args: Array&lt;unknown>) => void }  export default function ErrorFallback({ error, resetErrorBoundary }: Props) {   return (     &lt;Card       role='alert'       sx={{         display: 'flex',         flexDirection: 'column',         alignItems: 'center',         justifyContent: 'center',         width: 320,         mt: 2,         mx: 'auto',         pb: 2       }}     >       &lt;CardHeader title='Something went wrong' \/>       &lt;CardContent>         &lt;Typography variant='body1' color='error'>           {\/* \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435 *\/}           {error.message || 'Unknown error'}         &lt;\/Typography>       &lt;\/CardContent>       &lt;CardActions>         {\/* \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 *\/}         &lt;Button           variant='contained'           color='success'           onClick={resetErrorBoundary}         >           Reload         &lt;\/Button>       &lt;\/CardActions>     &lt;\/Card>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u0434\u0432\u0430\u043b \u0441\u0430\u0439\u0442\u0430 (<code>components\/Footer.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { Box, Typography } from '@mui\/material'  export default function Footer() {   return (     &lt;Box component='footer' p={1} bgcolor='primary.main'>       &lt;Typography variant='body2' textAlign='center' color='white'>         {new Date().getFullYear()}. &amp;copy; All rights reserved       &lt;\/Typography>     &lt;\/Box>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0428\u0430\u043f\u043a\u0430 \u0441\u0430\u0439\u0442\u0430 (<code>components\/Header.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { AppBar } from '@mui\/material' import DesktopMenu from '.\/Menu\/Desktop' import MobileMenu from '.\/Menu\/Mobile'  export type PageLinks = { title: string; href: string }[]  \/\/ \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 3 \u0441\u0442\u0440\u0430\u043d\u0438\u0446: \/\/ \u0413\u043b\u0430\u0432\u043d\u043e\u0439, \u0411\u043b\u043e\u0433\u0430 \u0438 \u041a\u043e\u043d\u0442\u0430\u043a\u0442\u043e\u0432 const PAGE_LINKS = [   { title: 'Home', href: '\/' },   { title: 'Posts', href: '\/posts' },   { title: 'About', href: '\/about' } ]  export default function Header() {   return (     &lt;AppBar position='relative'>       {\/* \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0448\u0438\u0440\u0438\u043d\u044b \u044d\u043a\u0440\u0430\u043d\u0430 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u0441\u044f \u043b\u0438\u0431\u043e \u0434\u0435\u0441\u043a\u0442\u043e\u043f\u043d\u043e\u0435 \u043c\u0435\u043d\u044e, \u043b\u044e\u0431\u043e \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 *\/}       &lt;DesktopMenu links={PAGE_LINKS} \/>       &lt;MobileMenu links={PAGE_LINKS} \/>     &lt;\/AppBar>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0435\u0441\u043a\u0442\u043e\u043f\u043d\u043e\u0435 \u043c\u0435\u043d\u044e (<code>components\/Menu\/Desktop.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { List, ListItem } from '@mui\/material' import { useTheme } from '@mui\/material\/styles' import ActiveLink from '..\/ActiveLink' import ProfileButton from '..\/Buttons\/Profile' import type { PageLinks } from '..\/Header'  type Props = {   links: PageLinks }  export default function DesktopMenu({ links }: Props) {   const theme = useTheme()    return (     &lt;List       sx={{         \/\/ \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u044c\u044e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0448\u0438\u0440\u0438\u043d\u044b \u044d\u043a\u0440\u0430\u043d\u0430         display: { xs: 'none', sm: 'flex' },         justifyContent: 'flex-end',         paddingInline: theme.spacing(1)       }}     >       {links.map((link, i) => (         &lt;ListItem key={i}>           &lt;ActiveLink href={link.href} activeClassName='current'>             {link.title}           &lt;\/ActiveLink>         &lt;\/ListItem>       ))}       &lt;ProfileButton \/>     &lt;\/List>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u0441\u044b\u043b\u043e\u043a \u0438 \u043a\u043d\u043e\u043f\u043a\u0443 \u043f\u0440\u043e\u0444\u0438\u043b\u044f.<\/p>\n<p>  <\/p>\n<p>\u041c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043c\u0435\u043d\u044e (<code>components\/Menu\/Mobile.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import MenuIcon from '@mui\/icons-material\/Menu' import { Box, Drawer, List, ListItem, ListItemButton } from '@mui\/material' import { useTheme } from '@mui\/material\/styles' import { useState } from 'react' import ActiveLink from '..\/ActiveLink' import ProfileButton from '..\/Buttons\/Profile' import type { PageLinks } from '..\/Header'  type Props = {   links: PageLinks }  export default function MobileMenu({ links }: Props) {   const theme = useTheme()   \/\/ \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u044f\u043a\u043e\u0440\u044c \u0434\u043b\u044f \u043c\u0435\u043d\u044e   const [anchorEl, setAnchorEl] = useState&lt;HTMLElement | null>(null)   \/\/ \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0441\u0442\u0438 \u043c\u0435\u043d\u044e   const open = Boolean(anchorEl)    \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u043c\u0435\u043d\u044e   const openMenu = (e: React.MouseEvent&lt;HTMLDivElement>) => {     setAnchorEl(e.currentTarget)   }    \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u044f \u043c\u0435\u043d\u044e   const closeMenu = () => {     setAnchorEl(null)   }    return (     &lt;Box       \/\/ \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u044c\u044e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0448\u0438\u0440\u0438\u043d\u044b \u044d\u043a\u0440\u0430\u043d\u0430       sx={{ display: { xs: 'flex', sm: 'none' } }}       alignItems='center'       justifyContent='space-between'     >       &lt;ListItemButton         id='menu-button'         sx={{ borderRadius: '50%', px: theme.spacing(1) }}         aria-controls={open ? 'mobile-menu' : undefined}         aria-haspopup='true'         aria-expanded={open ? 'true' : undefined}         onClick={openMenu}       >         &lt;MenuIcon \/>       &lt;\/ListItemButton>       &lt;Drawer anchor='left' open={open} onClose={closeMenu} id='mobile-menu'>         &lt;List sx={{ minWidth: '128px' }}>           {links.map((link, i) => (             &lt;ListItem               onClick={closeMenu}               key={i}               sx={{ justifyContent: 'center' }}             >               &lt;ActiveLink href={link.href} activeClassName='current'>                 {link.title}               &lt;\/ActiveLink>             &lt;\/ListItem>           ))}         &lt;\/List>       &lt;\/Drawer>       &lt;ProfileButton \/>     &lt;\/Box>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u0431\u043e\u043a\u043e\u0432\u0443\u044e \u043f\u0430\u043d\u0435\u043b\u044c \u0441\u043e \u0441\u043f\u0438\u0441\u043a\u043e\u043c \u0441\u0441\u044b\u043b\u043e\u043a (+ \u043a\u043d\u043e\u043f\u043a\u0430 \u0434\u043b\u044f \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u043c\u0435\u043d\u044e) \u0438 \u043a\u043d\u043e\u043f\u043a\u0443 \u043f\u0440\u043e\u0444\u0438\u043b\u044f. \u041e <code>ProfileButton<\/code> \u043c\u044b \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u043f\u0440\u043e \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e.<\/p>\n<p>  <\/p>\n<p>\u0421 \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0435\u043d\u0438\u044f, \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u043c\u044b \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442.<\/p>\n<p>  <\/p>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<\/p>\n<p>  <\/p>\n<p><em>\u0414\u0435\u0441\u043a\u0442\u043e\u043f<\/em><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/ym\/2w\/ez\/ym2wezspov7-vs5nbfiwxctoaoo.png\" data-src=\"https:\/\/habrastorage.org\/webt\/ym\/2w\/ez\/ym2wezspov7-vs5nbfiwxctoaoo.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p><em>\u041c\u043e\u0431\u0430\u0439\u043b (\u043c\u0435\u043d\u044e \u0437\u0430\u043a\u0440\u044b\u0442\u043e)<\/em><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/tr\/gq\/-9\/trgq-9oca_in-ye9es1x4xz93ww.png\" data-src=\"https:\/\/habrastorage.org\/webt\/tr\/gq\/-9\/trgq-9oca_in-ye9es1x4xz93ww.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p><em>\u041c\u043e\u0431\u0430\u0439\u043b (\u043c\u0435\u043d\u044e \u043e\u0442\u043a\u0440\u044b\u0442\u043e)<\/em><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/2i\/h6\/jd\/2ih6jdu2azabbcdilohpuq1qoh4.png\" data-src=\"https:\/\/habrastorage.org\/webt\/2i\/h6\/jd\/2ih6jdu2azabbcdilohpuq1qoh4.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<h2 id=\"generaciya-staticheskogo-kontenta\">\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430<\/h2>\n<p>  <\/p>\n<p>\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 (\u0438\u043b\u0438 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b) (static-site generation, SSG) \u2014 \u044d\u0442\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441, \u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0433\u043e\u0442\u043e\u0432\u0443\u044e \u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044e \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0443 (HTML) \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0441\u0431\u043e\u0440\u043a\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u0413\u043e\u0442\u043e\u0432\u043d\u043e\u0441\u0442\u044c \u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e, \u0432\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u043a\u043b\u0438\u0435\u043d\u0442 \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0432 \u043e\u0442\u0432\u0435\u0442 \u043d\u0430 \u0437\u0430\u043f\u0440\u043e\u0441, \u0432\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, \u0442\u0430\u043a\u0438\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0445\u043e\u0440\u043e\u0448\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u043c\u0438 \u0431\u043e\u0442\u0430\u043c\u0438 (SEO).<\/p>\n<p>  <\/p>\n<p>\u0421\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u0431\u044b\u0432\u0430\u0435\u0442 2 \u0432\u0438\u0434\u043e\u0432: \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0438 \u0431\u0435\u0437. \u0421\u0442\u0430\u0442\u0438\u043a\u0430 \u0431\u0435\u0437 \u0434\u0430\u043d\u043d\u044b\u0445 \u2014 \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0430. \u0421\u0442\u0430\u0442\u0438\u043a\u0430 \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u2014 \u044d\u0442\u043e \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0430, \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u0435, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0435 \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0441\u0431\u043e\u0440\u043a\u0438 (\u0434\u0430\u043d\u043d\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e, \u0442\u0430\u043a \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e). \u0415\u0449\u0435 \u0440\u0430\u0437: \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0434\u0430\u043d\u043d\u044b\u0445, \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u043d\u0430 \u043c\u043e\u043c\u0435\u043d\u0442 \u0441\u0431\u043e\u0440\u043a\u0438. \u041f\u043e \u043e\u0431\u0449\u0435\u043c\u0443 \u043f\u0440\u0430\u0432\u0438\u043b\u0443, \u044d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0441\u0432\u0435\u0436\u0438\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0431\u0435\u0437 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0439 \u0441\u0431\u043e\u0440\u043a\u0438. Next.js \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043e\u0431\u043e\u0439\u0442\u0438 \u044d\u0442\u043e \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u0441 \u0438\u043d\u043a\u0440\u0435\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0439 (\u0447\u0430\u0441\u0442\u0438\u0447\u043d\u043e\u0439) \u0440\u0435\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0435\u0439.<\/p>\n<p>  <\/p>\n<p>\u0412 \u043d\u0430\u0448\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c\u0438 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0433\u043b\u0430\u0432\u043d\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043e\u0432. \u0414\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043e\u0431\u0435\u0438\u0445 \u044d\u0442\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u0435. \u0414\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e. \u041f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u043e\u043d\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043c\u0435\u0436\u0434\u0443 \u0441\u0431\u043e\u0440\u043a\u0430\u043c\u0438. \u0414\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043e\u0432 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e (\u043d\u0430 <a href=\"https:\/\/jsonbin.io\/\">JSONBin.io<\/a>). \u041f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u043e\u043d\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043a\u0430\u0436\u0434\u044b\u0435 12 \u0447\u0430\u0441\u043e\u0432. \u0414\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043e\u0432 \u043a\u0430\u0436\u0434\u044b\u0435 12 \u0447\u0430\u0441\u043e\u0432 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0438\u043d\u043a\u0440\u0435\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u0440\u0435\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438.<\/p>\n<p>  <\/p>\n<h3 id=\"glavnaya-stranica\">\u0413\u043b\u0430\u0432\u043d\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430<\/h3>\n<p>  <\/p>\n<p>\u0413\u043b\u0430\u0432\u043d\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 (<code>pages\/index.tsx<\/code>) \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0441\u043b\u0430\u0439\u0434\u0435\u0440\u0430 \u0438 4 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445 \u0431\u043b\u043e\u043a\u043e\u0432 \u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0434\u0430\u043d\u043d\u044b\u0445, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 <code>public\/data\/home.json<\/code>. \u0414\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f <code>getStaticProps<\/code>, \u0430 \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u2014 \u043c\u043e\u0434\u0443\u043b\u044c Node.js <a href=\"https:\/\/nodejs.org\/api\/fs.html\">fs<\/a>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import Animate, { SLIDE_DIRECTION } from '@\/components\/AnimateIn' import CustomHead from '@\/components\/Head' import Slider from '@\/components\/Slider' import type { Blocks } from '@\/types' import { useUser } from '@\/utils\/swr' import { Box, Grid } from '@mui\/material' import Typography from '@mui\/material\/Typography' import type { GetStaticPropsContext, InferGetStaticPropsType } from 'next' import Image from 'next\/image' \/\/ \u043c\u043e\u0434\u0443\u043b\u0438 Node.js import { readFile } from 'node:fs\/promises' import { join } from 'node:path'  \/\/ \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b export default function Home({   data }: InferGetStaticPropsType&lt;typeof getStaticProps>) {   \/\/ \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0445 \u0431\u043b\u043e\u043a\u043e\u0432   const { blocks } = data   \/\/ \u043e\u0431 \u044d\u0442\u043e\u043c \u043f\u043e\u0437\u0436\u0435   const { user } = useUser()    return (     &lt;>       &lt;CustomHead title='Home Page' description='This is Home Page' \/>       &lt;Typography variant='h4' textAlign='center' py={2}>         Welcome, {user ? user.username || user.email : 'stranger'}       &lt;\/Typography>       {\/* \u0441\u043b\u0430\u0439\u0434\u0435\u0440 *\/}       &lt;Slider slides={blocks} \/>       {\/* \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0431\u043b\u043e\u043a\u0438 *\/}       &lt;Box my={2}>         {blocks.map((block, i) => (           {\/* \u0441\u0430\u043c\u043e\u043f\u0438\u0441\u043d\u0430\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 *\/}           &lt;Animate.SlideIn             key={block.id}             direction={i % 2 ? SLIDE_DIRECTION.RIGHT : SLIDE_DIRECTION.LEFT}           >             &lt;Grid container spacing={2} my={4}>               {i % 2 ? (                 &lt;>                   &lt;Grid item md={6}>                     &lt;Typography variant='h5'>{block.title}&lt;\/Typography>                     &lt;Typography variant='body1' mt={2}>                       {block.description}                     &lt;\/Typography>                   &lt;\/Grid>                   &lt;Grid item md={6}>                     &lt;Image                       width={1024}                       height={320}                       src={block.imgSrc}                       alt={block.imgAlt}                       style={{                         borderRadius: '6px'                       }}                     \/>                   &lt;\/Grid>                 &lt;\/>               ) : (                 &lt;>                   &lt;Grid item md={6}>                     &lt;Image                       width={1024}                       height={320}                       src={block.imgSrc}                       alt={block.imgAlt}                       style={{                         borderRadius: '6px'                       }}                     \/>                   &lt;\/Grid>                   &lt;Grid item md={6}>                     &lt;Typography variant='h5'>{block.title}&lt;\/Typography>                     &lt;Typography variant='body1' mt={2}>                       {block.description}                     &lt;\/Typography>                   &lt;\/Grid>                 &lt;\/>               )}             &lt;\/Grid>           &lt;\/Animate.SlideIn>         ))}       &lt;\/Box>     &lt;\/>   ) }  \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 export async function getStaticProps(ctx: GetStaticPropsContext) {   let data = {     blocks: [] as Blocks   }    \/\/ \u043f\u0443\u0442\u044c \u043a \u0434\u0430\u043d\u043d\u044b\u043c   const dataPath = join(process.cwd(), 'public\/data\/home.json')    try {     \/\/ \u0447\u0438\u0442\u0430\u0435\u043c \u0444\u0430\u0439\u043b     const dataJson = await readFile(dataPath, 'utf-8')     if (dataJson) {       \/\/ \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0441\u0442\u0440\u043e\u043a\u0438 JSON \u0432 \u043e\u0431\u044a\u0435\u043a\u0442 JS       data = JSON.parse(dataJson)     }   } catch (e) {     console.error(e)   }    \/\/ \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0432 \u0432\u0438\u0434\u0435 \u043f\u0440\u043e\u043f\u0430   return {     props: {       data     }   } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/hw\/ui\/_e\/hwui_e5uapmraiqhqsrvu42e26a.png\" data-src=\"https:\/\/habrastorage.org\/webt\/hw\/ui\/_e\/hwui_e5uapmraiqhqsrvu42e26a.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<h3 id=\"stranica-kontaktov\">\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043e\u0432<\/h3>\n<p>  <\/p>\n<p>\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043e\u0432 (<code>pages\/about.tsx<\/code>) \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0431\u043b\u043e\u043a\u0430 \u0441 \u043f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435\u043c \u0438 6 \u043d\u043e\u0432\u043e\u0441\u0442\u043d\u044b\u0445 \u0431\u043b\u043e\u043a\u043e\u0432 \u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u0434\u0430\u043d\u043d\u044b\u0445, \u0445\u0440\u0430\u043d\u044f\u0449\u0438\u0445\u0441\u044f \u043d\u0430 JSONBin.io. \u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f <code>fetch<\/code>. \u0423 \u043a\u0430\u0436\u0434\u043e\u0439 \u043d\u043e\u0432\u043e\u0441\u0442\u0438 \u0438\u043c\u0435\u0435\u0442\u0441\u044f \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 (<code>pages\/news\/[id].tsx<\/code>). \u0414\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043e\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f <code>getStaticProps<\/code>. \u0410 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043d\u043e\u0432\u043e\u0441\u0442\u0438 \u2014 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>getStaticProps<\/code> \u0438 <code>getStaticPaths<\/code>. <code>getStaticPaths<\/code> \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u0442 Next.js \u043e \u0442\u043e\u043c, \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0443 \u043d\u0430\u0441 \u043d\u043e\u0432\u043e\u0441\u0442\u0435\u0439, \u0442.\u0435. \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043d\u043e\u0432\u043e\u0441\u0442\u043d\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0441\u0431\u043e\u0440\u043a\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043e\u0432 (<code>pages\/about.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import Animate from '@\/components\/AnimateIn' import CustomHead from '@\/components\/Head' import NewsPreview from '@\/components\/NewsPreview' import type { NewsArr } from '@\/types' import { Grid, Typography } from '@mui\/material' import type { GetStaticPropsContext, InferGetStaticPropsType } from 'next'  \/\/ \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b export default function About({   data }: InferGetStaticPropsType&lt;typeof getStaticProps>) {   \/\/ \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u043e\u0432\u043e\u0441\u0442\u043d\u044b\u0445 \u0431\u043b\u043e\u043a\u043e\u0432   const { news } = data    return (     &lt;>       &lt;CustomHead title='About Page' description='This is About Page' \/>       &lt;Typography variant='h4' textAlign='center' py={2}>         About       &lt;\/Typography>       {\/* \u0431\u043b\u043e\u043a \u0441 \u043f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435\u043c *\/}       &lt;Typography variant='body1'>         Lorem ipsum dolor, sit amet consectetur adipisicing elit. Doloribus,         obcaecati necessitatibus! Doloremque numquam magni culpa atque omnis         ipsa sequi, nostrum, provident repudiandae sint aperiam temporibus nulla         minima quas rem ex autem dolores consequuntur! Officia laborum autem ex         eius cumque non aspernatur blanditiis commodi quae magnam ipsa qui sunt         dolor quos dolorum eveniet, nobis excepturi voluptatum quasi, dicta sit         aut, corporis hic. Magni numquam, accusamus, quasi consectetur facere         quod consequuntur aliquid illo commodi ducimus id tenetur ea molestiae         suscipit itaque assumenda ex. Expedita rem architecto itaque, ad         voluptate nesciunt nisi veniam modi cupiditate, amet id velit deserunt         soluta? Ex, voluptate libero.       &lt;\/Typography>       &lt;Typography variant='h5' textAlign='center' py={2}>         News       &lt;\/Typography>       {\/* \u043d\u043e\u0432\u043e\u0441\u0442\u043d\u044b\u0435 \u0431\u043b\u043e\u043a\u0438 *\/}       {\/* \u043f\u0440\u0435\u0432\u044c\u044e \u043d\u043e\u0432\u043e\u0441\u0442\u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 *\/}       &lt;Grid container spacing={2} pb={2}>         {news.map((n) => (           &lt;Grid item md={6} lg={4} key={n.id}>             &lt;Animate.FadeIn>               &lt;NewsPreview news={n} \/>             &lt;\/Animate.FadeIn>           &lt;\/Grid>         ))}       &lt;\/Grid>     &lt;\/>   ) }  \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 export async function getStaticProps(ctx: GetStaticPropsContext) {   let data = {     news: [] as NewsArr   }    try {     const response = await fetch(       `https:\/\/api.jsonbin.io\/v3\/b\/${process.env.JSONBIN_BIN_ID}?meta=false`,       {         headers: {           'X-Master-Key': process.env.JSONBIN_X_MASTER_KEY         }       }     )     if (!response.ok) {       throw response     }     data = await response.json()   } catch (e) {     console.error(e)   }    return {     props: {       data     },     \/\/ \u0434\u0430\u043d\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0438\u043d\u043a\u0440\u0435\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u0443\u044e \u0440\u0435\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044e     \/\/ \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u0440\u0435\u043c\u044f \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445 - 12 \u0447\u0430\u0441\u043e\u0432     revalidate: 60 * 60 * 12   } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 <code>revalidate<\/code> \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0441\u0431\u043e\u0440\u043a\u0438 \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043a\u0430\u0436\u0434\u044b\u0435 12 \u0447\u0430\u0441\u043e\u0432. \u042d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<p>  <\/p>\n<ul>\n<li>\u041e\u0442\u0432\u0435\u0442 \u043d\u0430 \u043b\u044e\u0431\u043e\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u043a \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043e\u0432 \u0434\u043e \u0438\u0441\u0442\u0435\u0447\u0435\u043d\u0438\u044f 12 \u0447\u0430\u0441\u043e\u0432 \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f (\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f) \u0438\u0437 \u043a\u044d\u0448\u0430;<\/li>\n<li>\u043f\u043e \u0438\u0441\u0442\u0435\u0447\u0435\u043d\u0438\u0438 12 \u0447\u0430\u0441\u043e\u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0432 \u043e\u0442\u0432\u0435\u0442 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b;<\/li>\n<li>\u043f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0432 \u0444\u043e\u043d\u043e\u0432\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0440\u0435\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b (\u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f <code>getStaticProps()<\/code> \u0438 \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043d\u043e\u0432\u0430\u044f \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0430);<\/li>\n<li>\u043f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0440\u0435\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043a\u044d\u0448 \u0438\u043d\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u043d\u043e\u0432\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430. \u041f\u0440\u0438 \u043f\u0440\u043e\u0432\u0430\u043b\u0435 \u0440\u0435\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0442\u0430\u0440\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u043d\u0435\u0438\u0437\u043c\u0435\u043d\u043d\u043e\u0439.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u043e\u0432\u043e\u0441\u0442\u0438 (<code>pages\/news\/[id].tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import CustomHead from '@\/components\/Head' import type { News, NewsArr } from '@\/types' import ArrowBackIosNewIcon from '@mui\/icons-material\/ArrowBackIosNew' import {   Avatar,   Box,   Button,   Card,   CardContent,   CardHeader,   CardMedia,   Typography } from '@mui\/material' import { blue, red } from '@mui\/material\/colors' import type {   GetStaticPathsContext,   GetStaticPropsContext,   InferGetStaticPropsType } from 'next' import Link from 'next\/link'  \/\/ \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b export default function ArticlePage({   news }: InferGetStaticPropsType&lt;typeof getStaticProps>) {   return (     &lt;>       &lt;CustomHead title={news.title} description={news.text.slice(0, 10)} \/>       &lt;Box py={2}>         &lt;Card>           &lt;CardHeader             avatar={               &lt;Avatar                 sx={{ bgcolor: news.id % 2 === 0 ? red[500] : blue[500] }}                 aria-label='author avatar'               >                 {news.author.slice(0, 1)}               &lt;\/Avatar>             }             action={               &lt;Link href='\/about'>                 &lt;Button aria-label='return to about page'>                   &lt;ArrowBackIosNewIcon fontSize='small' \/>                   &lt;Typography variant='body2'>Back&lt;\/Typography>                 &lt;\/Button>               &lt;\/Link>             }             title={news.title}             subheader={new Date(news.datePublished).toDateString()}           \/>           &lt;CardMedia             component='img'             height='300'             image={news.imgSrc}             alt={news.imgAlt}           \/>           &lt;CardContent>             &lt;Typography variant='body1'>{news.text}&lt;\/Typography>           &lt;\/CardContent>         &lt;\/Card>       &lt;\/Box>     &lt;\/>   ) }  \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043f\u0443\u0442\u0435\u0439 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 export async function getStaticPaths(ctx: GetStaticPathsContext) {   let data = {     news: [] as NewsArr   }    try {     \/\/ \u0437\u0434\u0435\u0441\u044c \u043d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432\u0441\u0435\u0445 \u043d\u043e\u0432\u043e\u0441\u0442\u0435\u0439     const response = await fetch(       `https:\/\/api.jsonbin.io\/v3\/b\/${process.env.JSONBIN_BIN_ID}?meta=false`,       {         headers: {           'X-Master-Key': process.env.JSONBIN_X_MASTER_KEY         }       }     )     if (!response.ok) {       throw response     }     data = await response.json()   } catch (e) {     console.error(e)   }    \/\/ \u043f\u0443\u0442\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446   const paths = data.news.map((n) => ({     params: { id: String(n.id) }   }))    \/\/ \u0412\u043e \u0432\u0440\u0435\u043c\u044f \u0441\u0431\u043e\u0440\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u0442\u0440\u0435\u043d\u0434\u0435\u0440\u0435\u043d\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0441 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u043c\u0438 \u043f\u0443\u0442\u044f\u043c\u0438   \/\/ `{ fallback: 'blocking' }` \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e Next.js \u043f\u043e\u043f\u044b\u0442\u0430\u0435\u0442\u0441\u044f   \/\/ \u043e\u0442\u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u044c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u043e \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u043c\u0443 \u043f\u0443\u0442\u0438 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435   return {     paths,     fallback: 'blocking'   } }  export async function getStaticProps({   params }: GetStaticPropsContext&lt;{ id: string }>) {   let news = {} as News    try {     \/\/ \u0437\u0434\u0435\u0441\u044c \u043d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u043e\u0439 \u043d\u043e\u0432\u043e\u0441\u0442\u0438     const response = await fetch(       `https:\/\/api.jsonbin.io\/v3\/b\/${process.env.JSONBIN_BIN_ID}?meta=false`,       {         headers: {           'X-Master-Key': process.env.JSONBIN_X_MASTER_KEY,           'X-JSON-Path': `news[${Number(params?.id) - 1}]`         }       }     )     if (!response.ok) {       throw response     }     const data = await response.json()     news = data[0]     \/\/ \u0432\u0430\u0436\u043d\u043e!     \/\/ \u0435\u0441\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u043e\u0432\u043e\u0441\u0442\u0438 \u0441 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u043c id \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0442,     \/\/ \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 404     if (!news) {       return {         notFound: true       }     }   } catch (e) {     console.error(e)   }    return {     props: {       news     },     \/\/ \u0438\u043d\u043a\u0440\u0435\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u0440\u0435\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f     revalidate: 60 * 60 * 12   } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<\/p>\n<p>  <\/p>\n<p><em>\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043e\u0432<\/em><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/j4\/wp\/dy\/j4wpdyqfjuxkyi_x6xekhjeatlm.png\" data-src=\"https:\/\/habrastorage.org\/webt\/j4\/wp\/dy\/j4wpdyqfjuxkyi_x6xekhjeatlm.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p><em>\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u043e\u0432\u043e\u0441\u0442\u0438<\/em><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/3h\/h1\/2j\/3hh12jolxmgqphm70s-yhvm20ca.png\" data-src=\"https:\/\/habrastorage.org\/webt\/3h\/h1\/2j\/3hh12jolxmgqphm70s-yhvm20ca.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<h2 id=\"autentifikaciya-avtorizaciya-i-zagruzka-faylov\">\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f, \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0444\u0430\u0439\u043b\u043e\u0432<\/h2>\n<p>  <\/p>\n<p>\u041f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442 \u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u042d\u0442\u043e \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435, \u0437\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u043c \u043a\u043e\u0442\u043e\u0440\u044b\u0445 &#171;\u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0435\u0442&#187; \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435. \u0417\u0430\u043f\u0440\u043e\u0441 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/swr.vercel.app\/\">SWR<\/a>. SWR \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438 \u043c\u0443\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0445 \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u043e\u0441\u043b\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f SWR \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043e\u0431\u043e\u0439\u0442\u0438\u0441\u044c \u0431\u0435\u0437 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (state manager).<\/p>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u044e \u043d\u0430\u0434 SWR \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u0444\u0430\u0439\u043b\u0435 <code>utils\/swr.ts<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import type { User } from '@prisma\/client' import useSWRImmutable from 'swr\/immutable'  async function fetcher&lt;T>(   input: RequestInfo | URL,   init?: RequestInit | undefined ): Promise&lt;T> {   return fetch(input, init).then((res) => res.json()) }  \/\/ \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043e\u0434\u0438\u043d \u0440\u0430\u0437 export function useUser() {   \/\/ \u0443\u0442\u0438\u043b\u0438\u0442\u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u043e\u0448\u0438\u0431\u043a\u0443 \u0438   \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0438\u043d\u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u043a\u044d\u0448\u0430 (\u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043c\u0443\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445, \u0445\u0440\u0430\u043d\u044f\u0449\u0438\u0445\u0441\u044f \u0432 \u043a\u044d\u0448\u0435)   const { data, error, mutate } = useSWRImmutable&lt;any>(     '\/api\/auth\/user',     (url) => fetcher(url, { credentials: 'include' }),     {       onErrorRetry(err, key, config, revalidate, revalidateOpts) {         return false       }     }   )    \/\/ `error` - \u043e\u0431\u044b\u0447\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 (\u043d\u0435\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u043e\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435)   \/\/ `data.message` - \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:   \/\/ res.status(404).json({ message: 'User not found' })   if (error || data?.message) {     console.log(error || data?.message)      return {       user: undefined,       accessToken: undefined,       mutate     }   }    return {     user: data?.user as User,     accessToken: data?.accessToken as string,     mutate   } }<\/code><\/pre>\n<p>  <\/p>\n<h3 id=\"autentifikaciya-i-avtorizaciya\">\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f<\/h3>\n<p>  <\/p>\n<p>\u0412 \u0448\u0430\u043f\u043a\u0435 \u0441\u0430\u0439\u0442\u0435 \u0438\u043c\u0435\u0435\u0442\u0441\u044f \u043a\u043d\u043e\u043f\u043a\u0430 \u043f\u0440\u043e\u0444\u0438\u043b\u044f (<code>ProfileButton<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { useUser } from '@\/utils\/swr' import { Avatar, ListItemButton } from '@mui\/material' import { useTheme } from '@mui\/material\/styles' import AuthTabs from '..\/AuthTabs' import Modal from '..\/Modal' import UserPanel from '..\/UserPanel'  export default function ProfileButton() {   \/\/ \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f   const { user } = useUser()   const theme = useTheme()    \/\/ \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u043c\u043e\u0434\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u043a\u043d\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u043d\u0430\u043b\u0438\u0447\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f   const modalContent = user ? &lt;UserPanel \/> : &lt;AuthTabs \/>    return (     &lt;Modal       \/\/ \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442, \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442 \u043a \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044e \u043c\u043e\u0434\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u043a\u043d\u0430       triggerComponent={         &lt;ListItemButton sx={{ borderRadius: '50%', px: theme.spacing(1) }}>           &lt;Avatar             \/\/ \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c \u0430\u0432\u0430\u0442\u0430\u0440\u0430 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u0438\u0431\u043e \u0444\u0430\u0439\u043b, \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u043b\u0438\u0431\u043e \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435             src={user &amp;&amp; user.avatarUrl ? user.avatarUrl : '\/img\/user.png'}           \/>         &lt;\/ListItemButton>       }       modalContent={modalContent}     \/>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438, \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0430\u0432\u0430\u0442\u0430\u0440\u043e\u0432 \u0438 \u0432\u044b\u0445\u043e\u0434\u0430 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u043d \u0432 \u043c\u043e\u0434\u0430\u043b\u044c\u043d\u043e\u043c \u043e\u043a\u043d\u0435 (<code>components\/Modal.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import CloseIcon from '@mui\/icons-material\/Close' import { Box, IconButton, Modal as MuiModal } from '@mui\/material' import { cloneElement, useMemo, useState } from 'react'  type Props = {   triggerComponent: JSX.Element   modalContent: JSX.Element   size?: 'S' | 'M' }  export default function Modal({   triggerComponent,   modalContent,   size = 'S' }: Props) {   \/\/ \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0441\u0442\u0438 \u043c\u043e\u0434\u0430\u043b\u043a\u0438   const [open, setOpen] = useState(false)    \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u043c\u043e\u0434\u0430\u043b\u043a\u0438   const handleOpen = () => setOpen(true)   \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u044f \u043c\u043e\u0434\u0430\u043b\u043a\u0438   const handleClose = () => setOpen(false)    \/\/ \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u043c\u0443 \u043c\u043e\u0434\u0430\u043b\u043a\u0438 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u043e\u043f\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u044f \u043c\u043e\u0434\u0430\u043b\u043a\u0438   const content = cloneElement(modalContent, { closeModal: handleClose })    const modalStyles = useMemo(     () => ({       bgcolor: 'background.paper',       borderRadius: 1,       boxShadow: 24,       left: '50%',       maxWidth: size === 'S' ? 425 : 576,       p: 2,       position: 'absolute' as 'absolute',       top: '50%',       transform: 'translate(-50%, -50%)',       width: '100%',       outline: 'none'     }),     [size]   )    return (     &lt;>       &lt;Box onClick={handleOpen}>{triggerComponent}&lt;\/Box>       &lt;MuiModal open={open} onClose={handleClose}>         &lt;Box sx={modalStyles}>           &lt;IconButton             sx={{               position: 'absolute',               top: '1rem',               right: '1rem'             }}             onClick={handleClose}           >             &lt;CloseIcon \/>           &lt;\/IconButton>           {content}         &lt;\/Box>       &lt;\/MuiModal>     &lt;\/>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0440\u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u044b\u043c \u043c\u043e\u0434\u0430\u043b\u043a\u0438 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 (<code>components\/AuthTabs.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import storageLocal from '@\/utils\/storageLocal' import { Box, Tab, Tabs } from '@mui\/material' import { useEffect, useState } from 'react' import LoginForm from '.\/Forms\/Login' import RegisterForm from '.\/Forms\/Register'  type TabPanelProps = {   children?: React.ReactNode   index: number   value: number }  function TabPanel({ children, value, index, ...otherProps }: TabPanelProps) {   return (     &lt;Box       aria-labelledby={`auth-tab-${index}`}       display={value === index ? 'block' : 'none'}       hidden={value !== index}       id={`auth-tabpanel-${index}`}       role='tabpanel'       {...otherProps}     >       {value === index &amp;&amp; children}     &lt;\/Box>   ) }  function a11yProps(index: number) {   return {     id: `auth-tab-${index}`,     'aria-controls': `auth-tabpanel-${index}`   } }  type Props = { closeModal?: () => void }  export default function AuthTabs({ closeModal }: Props) {   \/\/ \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0438   const [tabIndex, setTabIndex] = useState(0)   \/\/ \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438   const [loading, setLoading] = useState(true)    \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0432\u043a\u043b\u0430\u0434\u043e\u043a   const handleChange = (event: React.SyntheticEvent, value: number) => {     setTabIndex(value)   }    \/\/ \u043f\u043e\u0441\u043b\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043c\u044b \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u0411\u0414,   \/\/ \u043d\u043e \u0442\u0430\u043a\u0436\u0435 \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u043c \u0444\u0430\u043a\u0442 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u043c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435   \/\/ \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d, \u043c\u044b \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0435\u043c\u0443 \u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438,   \/\/ \u0435\u0441\u043b\u0438 \u043d\u0435\u0442 - \u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438   useEffect(() => {     if (storageLocal.get('user_has_been_registered')) {       setTabIndex(1)     }     setLoading(false)   }, [])    if (loading) return null    return (     &lt;>       &lt;Box display='flex'>         &lt;Tabs           value={tabIndex}           onChange={handleChange}           aria-label='auth tabs'         >           &lt;Tab label='Register' {...a11yProps(0)} \/>           &lt;Tab label='Login' {...a11yProps(1)} \/>         &lt;\/Tabs>       &lt;\/Box>       &lt;TabPanel value={tabIndex} index={0}>         &lt;RegisterForm closeModal={closeModal} \/>       &lt;\/TabPanel>       &lt;TabPanel value={tabIndex} index={1}>         &lt;LoginForm closeModal={closeModal} \/>       &lt;\/TabPanel>     &lt;\/>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0424\u043e\u0440\u043c\u0430 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 (<code>components\/Forms\/Register.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import type { UserResponseData } from '@\/types' import storageLocal from '@\/utils\/storageLocal' import { useUser } from '@\/utils\/swr' import MailOutlineIcon from '@mui\/icons-material\/MailOutline' import PersonOutlineIcon from '@mui\/icons-material\/PersonOutline' import VpnKeyIcon from '@mui\/icons-material\/VpnKey' import {   Button,   FormControl,   FormHelperText,   Input,   InputLabel,   Typography } from '@mui\/material' import { useTheme } from '@mui\/material\/styles' import type { User } from '@prisma\/client' import { useRouter } from 'next\/router' import { useState } from 'react' import FormFieldsWrapper from '.\/Wrapper'  type Props = {   closeModal?: () => void }  export default function RegisterForm({ closeModal }: Props) {   const theme = useTheme()   const router = useRouter()   \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043c\u0443\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f   const { mutate } = useUser()    \/\/ \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043e\u0448\u0438\u0431\u043e\u043a   const [errors, setErrors] = useState&lt;{     email?: boolean     password?: boolean     passwordConfirm?: boolean   }>({})    \/\/ \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0444\u043e\u0440\u043c\u044b   const handleSubmit: React.FormEventHandler&lt;HTMLFormElement> = async (e) => {     e.preventDefault()     \/\/ \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u0432\u0438\u0434\u0435 \u043e\u0431\u044a\u0435\u0442\u0430     const formData = Object.fromEntries(       new FormData(e.target as HTMLFormElement)     ) as unknown as Pick&lt;User, 'username' | 'email' | 'password'> &amp; {       passwordConfirm?: string     }      \/\/ \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0444\u043e\u0440\u043c\u044b     const _errors: typeof errors = {}     if (formData.password.length &lt; 6) {       _errors.password = true     }     if (formData.password !== formData.passwordConfirm) {       _errors.passwordConfirm = true     }     \/\/ \u0435\u0441\u043b\u0438 \u0438\u043c\u0435\u044e\u0442\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u0438     if (Object.keys(_errors).length) {       return setErrors({ ..._errors })     }      \/\/ \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u043b\u0438\u0448\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435     delete formData.passwordConfirm      try {       \/\/ \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440       const res = await fetch('\/api\/auth\/register', {         method: 'POST',         body: JSON.stringify(formData)       })        \/\/ \u0435\u0441\u043b\u0438 \u043e\u0442\u0432\u0435\u0442 \u0438\u043c\u0435\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441-\u043a\u043e\u0434 409,       \/\/ \u0437\u043d\u0430\u0447\u0438\u0442, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d       if (res.status === 409) {         return setErrors({ email: true })       } else if (!res.ok) {         throw res       }        \/\/ \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438\u0437 \u043e\u0442\u0432\u0435\u0442\u0430       const data = await res.json() as UserResponseData       \/\/ \u0438\u043d\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u043a\u044d\u0448       mutate(data)       \/\/ \u0444\u0438\u043a\u0441\u0438\u0440\u0443\u0435\u043c \u0444\u0430\u043a\u0442 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u043c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435       storageLocal.set('user_has_been_registered', true)        \/\/ \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043c\u043e\u0434\u0430\u043b\u043a\u0443       if (closeModal) {         closeModal()       }        \/\/ \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0430 \u0433\u043b\u0430\u0432\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443       if (router.pathname !== '\/') {         router.push('\/')       }     } catch (e) {       console.error(e)     }   }    \/\/ \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0432\u0432\u043e\u0434\u0430   const handleInput: React.FormEventHandler&lt;HTMLFormElement> = () => {     \/\/ \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0438 \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438     if (Object.keys(errors).length) {       setErrors({})     }   }    return (     &lt;FormFieldsWrapper handleSubmit={handleSubmit} handleInput={handleInput}>       &lt;Typography variant='h4'>Register&lt;\/Typography>       &lt;FormControl required>         &lt;InputLabel htmlFor='username'>Username&lt;\/InputLabel>         &lt;Input           sx={{ gap: theme.spacing(1) }}           id='username'           name='username'           startAdornment={&lt;PersonOutlineIcon \/>}         \/>       &lt;\/FormControl>       &lt;FormControl required error={errors.email}>         &lt;InputLabel htmlFor='email'>Email&lt;\/InputLabel>         &lt;Input           sx={{ gap: theme.spacing(1) }}           id='email'           type='email'           name='email'           startAdornment={&lt;MailOutlineIcon \/>}         \/>         {errors.email &amp;&amp; &lt;FormHelperText>Email already in use&lt;\/FormHelperText>}       &lt;\/FormControl>       &lt;FormControl required error={errors.password}>         &lt;InputLabel htmlFor='password'>Password&lt;\/InputLabel>         &lt;Input           sx={{ gap: theme.spacing(1) }}           id='password'           type='password'           name='password'           startAdornment={&lt;VpnKeyIcon \/>}         \/>         &lt;FormHelperText>           Password must be at least 6 characters long         &lt;\/FormHelperText>       &lt;\/FormControl>       &lt;FormControl required error={errors.passwordConfirm}>         &lt;InputLabel htmlFor='password-confirm'>Confirm password&lt;\/InputLabel>         &lt;Input           sx={{ gap: theme.spacing(1) }}           id='password-confirm'           type='password'           name='passwordConfirm'           startAdornment={&lt;VpnKeyIcon \/>}         \/>         {errors?.passwordConfirm &amp;&amp; (           &lt;FormHelperText>Passwords must be the same&lt;\/FormHelperText>         )}       &lt;\/FormControl>       &lt;Button type='submit' variant='contained' color='success'>         Register       &lt;\/Button>     &lt;\/FormFieldsWrapper>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0424\u043e\u0440\u043c\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u0447\u0442\u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0447\u043d\u0430 \u0444\u043e\u0440\u043c\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438.<\/p>\n<p>  <\/p>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<\/p>\n<p>  <\/p>\n<p><em>\u0424\u043e\u0440\u043c\u0430 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438<\/em><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/-p\/7y\/h-\/-p7yh-lawndayohrqs42jynzfsw.png\" data-src=\"https:\/\/habrastorage.org\/webt\/-p\/7y\/h-\/-p7yh-lawndayohrqs42jynzfsw.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p><em>\u0424\u043e\u0440\u043c\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438<\/em><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/2f\/_g\/vt\/2f_gvtqirhyrccwvkbarncirpmu.png\" data-src=\"https:\/\/habrastorage.org\/webt\/2f\/_g\/vt\/2f_gvtqirhyrccwvkbarncirpmu.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<h3 id=\"polzovatelskaya-panel\">\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0430\u044f \u043f\u0430\u043d\u0435\u043b\u044c<\/h3>\n<p>  <\/p>\n<p>\u041f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u044b\u043c \u043c\u043e\u0434\u0430\u043b\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u0441\u044f \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u043a\u043d\u043e\u043f\u043a\u0438 \u043f\u0440\u043e\u0444\u0438\u043b\u044f, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0430\u044f \u043f\u0430\u043d\u0435\u043b\u044c (<code>components\/UserPanel.tsx<\/code>), \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0430\u044f \u0444\u043e\u0440\u043c\u0443 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0430\u0432\u0430\u0442\u0430\u0440\u0430 \u0438 \u043a\u043d\u043e\u043f\u043a\u0443 \u0434\u043b\u044f \u0432\u044b\u0445\u043e\u0434\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { Divider } from '@mui\/material' import LogoutButton from '.\/Buttons\/Logout' import UploadForm from '.\/Forms\/Upload'  type Props = {   closeModal?: () => void }  export default function UserPanel({ closeModal }: Props) {   return (     &lt;>       &lt;UploadForm closeModal={closeModal} \/>       &lt;Divider \/>       &lt;LogoutButton closeModal={closeModal} \/>     &lt;\/>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0424\u043e\u0440\u043c\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0430\u0432\u0430\u0442\u0430\u0440\u0430 (<code>components\/Forms\/Upload.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { useUser } from '@\/utils\/swr' import { Avatar, Box, Button, Typography } from '@mui\/material' import { useRef, useState } from 'react' import FormFieldsWrapper from '.\/Wrapper'  type Props = {   closeModal?: () => void }  export default function UploadForm({ closeModal }: Props) {   \/\/ \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u043f\u0440\u0435\u0432\u044c\u044e \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430   const previewRef = useRef&lt;HTMLImageElement | null>(null)   \/\/ \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430   const [file, setFile] = useState&lt;File>()   const { user, accessToken, mutate } = useUser()    if (!user) return null    \/\/ \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0444\u043e\u0440\u043c\u044b   const handleSubmit: React.FormEventHandler&lt;HTMLFormElement> = async (e) => {     if (!file) return      e.preventDefault()      const formData = new FormData()      \/\/ \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 `File`, \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f id \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f + \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430     const _file = new File([file], `${user.id}.${file.type.split('\/')[1]}`, {       type: file.type     })     formData.append('avatar', _file)      try {       \/\/ \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0444\u0430\u0439\u043b \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440       const res = await fetch('\/api\/upload', {         method: 'POST',         body: formData,         headers: {           \/\/ \u0440\u043e\u0443\u0442 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0430\u0432\u0430\u0442\u0430\u0440\u0430 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u044b\u043c           Authorization: `Bearer ${accessToken}`         }       })        if (!res.ok) {         throw res       }        \/\/ \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f       const user = await res.json()       \/\/ \u0438\u043d\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u043a\u044d\u0448       mutate({ user })        \/\/ \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043c\u043e\u0434\u0430\u043b\u043a\u0443       if (closeModal) {         closeModal()       }     } catch (e) {       console.error(e)     }   }    \/\/ \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0438\u043d\u043f\u0443\u0442\u0430 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u0430   const handleChange: React.ChangeEventHandler&lt;HTMLInputElement> = (e) => {     if (e.target.files &amp;&amp; previewRef.current) {       \/\/ \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0444\u0430\u0439\u043b       const _file = e.target.files[0]       \/\/ \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435       setFile(_file)       \/\/ \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 `img`       const img = previewRef.current.children[0] as HTMLImageElement       \/\/ \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f       img.src = URL.createObjectURL(_file)       img.onload = () => {         \/\/ \u043e\u0447\u0438\u0449\u0430\u0435\u043c \u043f\u0430\u043c\u044f\u0442\u044c         URL.revokeObjectURL(img.src)       }     }   }    return (     &lt;FormFieldsWrapper handleSubmit={handleSubmit}>       &lt;Typography variant='h4'>Avatar&lt;\/Typography>       &lt;Box display='flex' alignItems='center' gap={2}>         &lt;input           accept='image\/*'           style={{ display: 'none' }}           id='avatar'           name='avatar'           type='file'           onChange={handleChange}         \/>         &lt;label htmlFor='avatar'>           &lt;Button component='span'>Choose file&lt;\/Button>         &lt;\/label>         &lt;Avatar alt='preview' ref={previewRef} src='\/img\/user.png' \/>         &lt;Button           type='submit'           variant='contained'           color='success'           disabled={!file}         >           Upload         &lt;\/Button>       &lt;\/Box>     &lt;\/FormFieldsWrapper>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u043d\u043e\u043f\u043a\u0430 \u0434\u043b\u044f \u0432\u044b\u0445\u043e\u0434\u0430 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u044b (<code>components\/Buttons\/Logout.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { useUser } from '@\/utils\/swr' import { Box, Button } from '@mui\/material'  type Props = {   closeModal?: () => void }  export default function LogoutButton({ closeModal }: Props) {   const { accessToken, mutate } = useUser()    \/\/ \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043a\u043d\u043e\u043f\u043a\u0438   const onClick = async () => {     try {       \/\/ \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0443 \u043e \u0432\u044b\u0445\u043e\u0434\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u044b       const response = await fetch('\/api\/auth\/logout', {         headers: {           \/\/ \u0440\u043e\u0443\u0442 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u044b\u043c           Authorization: `Bearer ${accessToken}`         }       })        if (!response.ok) {         throw response       }        \/\/ \u0438\u043d\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u043a\u044d\u0448       mutate({ user: undefined, accessToken: undefined })        \/\/ \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043c\u043e\u0434\u0430\u043b\u043a\u0443       if (closeModal) {         closeModal()       }     } catch (e) {       console.error(e)     }   }    return (     &lt;Box display='flex' justifyContent='flex-end' pt={2} pr={2}>       &lt;Button color='error' variant='contained' onClick={onClick}>         Logout       &lt;\/Button>     &lt;\/Box>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<\/p>\n<p>  <\/p>\n<p><em>\u0411\u0435\u0437 \u043f\u0440\u0435\u0432\u044c\u044e<\/em><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/ng\/z6\/4v\/ngz64vjrbzuxrj3p03su5gzuka4.png\" data-src=\"https:\/\/habrastorage.org\/webt\/ng\/z6\/4v\/ngz64vjrbzuxrj3p03su5gzuka4.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p><em>\u0421 \u043f\u0440\u0435\u0432\u044c\u044e<\/em><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/sb\/xg\/jo\/sbxgjokmrvsa12lbhfhgfmobmpk.png\" data-src=\"https:\/\/habrastorage.org\/webt\/sb\/xg\/jo\/sbxgjokmrvsa12lbhfhgfmobmpk.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0430\u0432\u0430\u0442\u0430\u0440 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u0432 \u0448\u0430\u043f\u043a\u0435 \u0441\u0430\u0439\u0442\u0435 \u043d\u0430 \u043c\u0435\u0441\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0438 \u043f\u0440\u043e\u0444\u0438\u043b\u044f.<\/p>\n<p>  <\/p>\n<h2 id=\"sozdanie-obnovlenie-udalenie-i-layk-postov\">\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435, \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435, \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0438 \u043b\u0430\u0439\u043a \u043f\u043e\u0441\u0442\u043e\u0432<\/h2>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0431\u043b\u043e\u0433\u0430 \u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u043f\u043e\u0441\u0442\u043e\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>getServerSideProps<\/code>. \u0414\u0430\u043d\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434 \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0431\u043b\u043e\u0433\u0430 (<code>pages\/posts\/index.tsx<\/code>) \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u0441\u044f \u043a\u043d\u043e\u043f\u043a\u0430 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0430 \u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0441\u0442\u043e\u0432 (\u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import Animate from '@\/components\/AnimateIn' import CreatePostButton from '@\/components\/Buttons\/CreatePost' import CustomHead from '@\/components\/Head' import PostPreview from '@\/components\/PostPreview' import prisma from '@\/utils\/prisma' import { Divider, Grid, Typography } from '@mui\/material' import type {   GetServerSidePropsContext,   InferGetServerSidePropsType } from 'next'  \/\/ \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b export default function Posts({   posts }: InferGetServerSidePropsType&lt;typeof getServerSideProps>) {   return (     &lt;>       &lt;CustomHead title='Blog Page' description='This is Blog Page' \/>       {\/* \u043a\u043d\u043e\u043f\u043a\u0430 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430 *\/}       &lt;CreatePostButton \/>       &lt;Divider \/>       &lt;Typography variant='h4' textAlign='center' py={2}>         Posts       &lt;\/Typography>       {\/* \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0441\u0442\u043e\u0432 \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u0438\u0445 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 *\/}       {posts.length ? (         &lt;Grid container spacing={2} pb={2}>           {posts.map((post) => (             &lt;Grid item md={6} lg={4} key={post.id}>               &lt;Animate.FadeIn>                 &lt;PostPreview post={post} \/>               &lt;\/Animate.FadeIn>             &lt;\/Grid>           ))}         &lt;\/Grid>       ) : (         &lt;Typography mt={2}>There are no posts yet&lt;\/Typography>       )}     &lt;\/>   ) }  \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0433\u043e \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430 export async function getServerSideProps(ctx: GetServerSidePropsContext) {   try {     \/\/ \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0432\u0441\u0435 \u043f\u043e\u0441\u0442\u044b \u0438\u0437 \u0411\u0414     const posts = await prisma.post.findMany({       select: {         id: true,         title: true,         content: true,         author: true,         authorId: true,         likes: true,         createdAt: true       }     })     return {       props: {         posts: posts.map((post) => ({           ...post,           \/\/ \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u0443\u044e \u0441 \u043d\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043e\u043c\u0441\u0442\u044c\u044e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 `Date`           createdAt: new Date(post.createdAt).toLocaleDateString()         }))       }     }   } catch (e) {     console.log(e)     return {       props: {         posts: []       }     }   } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u043d\u043e\u043f\u043a\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430 (<code>components\/Button\/CreatePost.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { useUser } from '@\/utils\/swr' import { Button } from '@mui\/material' import { toast } from 'react-toastify' import CreatePostForm from '..\/Forms\/CreatePost' import Modal from '..\/Modal'  \/\/ \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u0441\u044f \u043c\u043e\u0434\u0430\u043b\u043a\u0430 \u0441 \u0444\u043e\u0440\u043c\u043e\u0439 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430 \/\/ \u043f\u0440\u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u0441\u044f \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 export default function CreatePostButton() {   const { user } = useUser()    const onClick = () => {     toast('Authorization required', {       type: 'warning'     })   }    return user ? (     &lt;Modal       triggerComponent={         &lt;Button variant='contained' sx={{ my: 2 }}>           Create new post         &lt;\/Button>       }       modalContent={&lt;CreatePostForm \/>}       size='M'     \/>   ) : (     &lt;Button variant='contained' sx={{ my: 2 }} onClick={onClick}>       Create new post     &lt;\/Button>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0424\u043e\u0440\u043c\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430 (<code>components\/Forms\/CreatePost.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { useUser } from '@\/utils\/swr' import { CssVarsProvider } from '@mui\/joy\/styles' import Textarea from '@mui\/joy\/Textarea' import {   Box,   Button,   FormControl,   FormHelperText,   Input,   InputLabel,   Typography } from '@mui\/material' import { red } from '@mui\/material\/colors' import { useTheme } from '@mui\/material\/styles' import type { Post } from '@prisma\/client' import { useRouter } from 'next\/router' import { useState } from 'react' import FormFieldsWrapper from '.\/Wrapper'  type Props = {   closeModal?: () => void }  export default function CreatePostForm({ closeModal }: Props) {   const theme = useTheme()   const { user, accessToken } = useUser()   const router = useRouter()    \/\/ \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043e\u0448\u0438\u0431\u043e\u043a   const [errors, setErrors] = useState&lt;{     content?: number   }>({})    if (!user) return null    \/\/ \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0444\u043e\u0440\u043c\u044b   const handleSubmit: React.FormEventHandler&lt;HTMLFormElement> = async (e) => {     if (!user) return     e.preventDefault()     \/\/ \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u0441\u0442\u0430 \u0432 \u0432\u0438\u0434\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u0430     const formData = Object.fromEntries(       new FormData(e.target as HTMLFormElement)     ) as unknown as Pick&lt;Post, 'title' | 'content'>      \/\/ \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0444\u043e\u0440\u043c\u044b     if (formData.content.length &lt; 50) {       return setErrors({ content: formData.content.length })     }      try {       \/\/ \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u0441\u0442\u0430 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440       const response = await fetch('\/api\/post', {         method: 'POST',         body: JSON.stringify(formData),         headers: {           \/\/ \u0440\u043e\u0443\u0442 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u044b\u043c           Authorization: `Bearer ${accessToken}`         }       })        if (!response.ok) {         throw response       }        \/\/ \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u0441\u0442\u0430 \u0438\u0437 \u043e\u0442\u0432\u0435\u0442\u0430       const post = await response.json()        \/\/ \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u043e\u0441\u0442\u0430       router.push(`\/posts\/${post.id}`)        \/\/ \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043c\u043e\u0434\u0430\u043b\u043a\u0443       if (closeModal) {         closeModal()       }     } catch (e) {       console.error(e)     }   }    \/\/ \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0432\u0432\u043e\u0434\u0430   const onInput = () => {     if (Object.keys(errors).length) {       setErrors({ content: undefined })     }   }    return (     &lt;FormFieldsWrapper handleSubmit={handleSubmit}>       &lt;Typography variant='h4'>Create post&lt;\/Typography>       &lt;FormControl required>         &lt;InputLabel htmlFor='title'>Title&lt;\/InputLabel>         &lt;Input           sx={{ gap: theme.spacing(1) }}           id='title'           type='text'           name='title'           inputProps={{             minLength: 3           }}         \/>       &lt;\/FormControl>       &lt;Box>         &lt;InputLabel>           Content * &lt;Typography variant='body2'>(50 symbols min)&lt;\/Typography>           &lt;CssVarsProvider>             &lt;Textarea               name='content'               required               minRows={5}               sx={{ mt: 1 }}               onInput={onInput}               defaultValue='Lorem ipsum dolor sit amet consectetur adipisicing elit. Soluta sed dicta eos ratione dolores doloribus magni repellendus aliquid sit dolor harum nemo porro voluptate incidunt quidem, molestias quia cum sequi minima debitis quae magnam est eius quas! Similique, enim non ad facilis dolores nulla corrupti assumenda, harum, ipsa consequuntur pariatur!'             \/>           &lt;\/CssVarsProvider>         &lt;\/InputLabel>         {errors.content &amp;&amp; (           &lt;FormHelperText sx={{ color: red[500] }}>             {50 - errors.content} symbols left           &lt;\/FormHelperText>         )}       &lt;\/Box>       &lt;Button type='submit' variant='contained' color='success'>         Create       &lt;\/Button>     &lt;\/FormFieldsWrapper>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043f\u043e\u0441\u0442\u0430 (<code>pages\/posts\/[id].tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import EditPostButton from '@\/components\/Buttons\/EditPost' import LikePostButton from '@\/components\/Buttons\/LikePost' import RemovePostButton from '@\/components\/Buttons\/RemovePost' import CustomHead from '@\/components\/Head' import prisma from '@\/utils\/prisma' import { useUser } from '@\/utils\/swr' import ArrowBackIosNewIcon from '@mui\/icons-material\/ArrowBackIosNew' import {   Avatar,   Box,   Button,   Card,   CardActions,   CardContent,   CardHeader,   CardMedia,   Typography } from '@mui\/material' import type {   GetServerSidePropsContext,   InferGetServerSidePropsType } from 'next' import Link from 'next\/link'  \/\/ \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b export default function PostPage({   post }: InferGetServerSidePropsType&lt;typeof getServerSideProps>) {   const { user } = useUser()   \/\/ \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u043e\u0441\u0442\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e   const isPostBelongsToUser = user &amp;&amp; user.id === post.authorId    return (     &lt;>       &lt;CustomHead title={post.title} description={post.content.slice(0, 10)} \/>       &lt;Box py={2}>         &lt;Card>           &lt;CardHeader             avatar={&lt;Avatar src={post.author.avatarUrl || '\/img\/user.png'} \/>}             action={               &lt;Link href='\/posts'>                 &lt;Button aria-label='return to about page'>                   &lt;ArrowBackIosNewIcon fontSize='small' \/>                   &lt;Typography variant='body2'>Back&lt;\/Typography>                 &lt;\/Button>               &lt;\/Link>             }             title={post.title}             subheader={post.createdAt}           \/>           &lt;CardMedia             component='img'             height='200'             \/\/ \u0443 \u043d\u0435\u0442 \u0440\u043e\u0443\u0442\u0430 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u043f\u043e\u0441\u0442\u0430             image='https:\/\/images.unsplash.com\/photo-1486312338219-ce68d2c6f44d?ixlib=rb-4.0.3&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=crop&amp;w=1172&amp;q=80'             alt=''           \/>           &lt;CardContent>             &lt;Typography variant='body1'>{post.content}&lt;\/Typography>           &lt;\/CardContent>           {\/* \u043b\u0430\u0439\u043a\u0430\u0442\u044c \u043f\u043e\u0441\u0442\u044b \u043c\u043e\u0433\u0443\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 *\/}           &lt;CardActions>             &lt;Box display='flex' justifyContent='flex-end' gap={2} width='100%'>               &lt;LikePostButton post={post} \/>               {\/* \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0443\u0434\u0430\u043b\u044f\u0442\u044c \u043f\u043e\u0441\u0442\u044b \u043c\u043e\u0433\u0443\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u0437\u0434\u0430\u0432\u0448\u0438\u0435 \u0438\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 *\/}               {isPostBelongsToUser &amp;&amp; (                 &lt;>                   &lt;EditPostButton post={post} icon={false} \/>                   &lt;RemovePostButton                     postId={post.id}                     authorId={post.authorId}                     icon={false}                   \/>                 &lt;\/>               )}             &lt;\/Box>           &lt;\/CardActions>         &lt;\/Card>       &lt;\/Box>     &lt;\/>   ) }  \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0433\u043e \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430 export async function getServerSideProps({   params }: GetServerSidePropsContext&lt;{ id: string }>) {   try {     \/\/ \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u0441\u0442\u0430 \u043f\u043e id     const post = await prisma.post.findUnique({       where: {         id: params?.id       },       select: {         id: true,         title: true,         content: true,         author: true,         authorId: true,         likes: true,         createdAt: true       }     })     \/\/ \u0435\u0441\u043b\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u0441\u0442\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0442,     \/\/ \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 404     if (!post) {       return {         notFound: true       }     }     return {       props: {         post: {           ...post,           \/\/ \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043a\u0443, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u0443\u044e \u0441 \u043d\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043c\u043e\u0441\u0442\u044c\u044e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 `Date`           createdAt: new Date(post.createdAt).toLocaleDateString()         }       }     }   } catch (e) {     console.error(e)   } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u043d\u043e\u043f\u043a\u0430 \u043b\u0430\u0439\u043a\u0430 \u043f\u043e\u0441\u0442\u0430 (<code>components\/Buttons\/LikePost.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { useUser } from '@\/utils\/swr' import FavoriteIcon from '@mui\/icons-material\/Favorite' import FavoriteBorderIcon from '@mui\/icons-material\/FavoriteBorder' import { Badge, IconButton } from '@mui\/material' import type { Like, Post } from '@prisma\/client' import { useRouter } from 'next\/router'  type Props = {   post: Omit&lt;Post, 'createdAt' | 'updatedAt'> &amp; {     likes: Like[]     createdAt: string   } }  export default function LikePostButton({ post }: Props) {   const router = useRouter()   const { user, accessToken } = useUser()   if (!user) return null   \/\/ \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c, \u043b\u0430\u0439\u043a\u0430\u043b \u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u044d\u0442\u043e\u0442 \u043f\u043e\u0441\u0442   const like = post.likes.find((l) => l.userId === user.id)   const isLiked = Boolean(like)    \/\/ \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043b\u0430\u0439\u043a\u0430\u043b \u043f\u043e\u0441\u0442, \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u043b\u0430\u0439\u043a   \/\/ \u0435\u0441\u043b\u0438 \u043d\u0435\u0442, \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043b\u0430\u0439\u043a   \/\/ \u043e\u0431\u0430 \u0440\u043e\u0443\u0442\u0430 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u044b\u043c\u0438   const likePost = async () => {     let res: Response     try {       if (isLiked) {         res = await fetch(`\/api\/like?likeId=${like?.id}&amp;postId=${post.id}`, {           method: 'DELETE',           headers: {             Authorization: `Bearer ${accessToken}`           }         })       } else {         res = await fetch('\/api\/like', {           method: 'POST',           body: JSON.stringify({ postId: post.id }),           headers: {             Authorization: `Bearer ${accessToken}`           }         })       }       if (!res.ok) throw res       \/\/ \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0434\u043b\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430 `getServerSideProps`       router.push(router.asPath)     } catch (e) {       console.log(e)     }   }    return (     &lt;Badge       badgeContent={post.likes.length}       color='error'       anchorOrigin={{         vertical: 'top',         horizontal: 'left'       }}     >       &lt;IconButton onClick={likePost}>         {isLiked ? &lt;FavoriteIcon color='error' \/> : &lt;FavoriteBorderIcon \/>}       &lt;\/IconButton>     &lt;\/Badge>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u043d\u043e\u043f\u043a\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430 (<code>components\/Buttons\/RemovePost.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { useUser } from '@\/utils\/swr' import DeleteOutlineIcon from '@mui\/icons-material\/DeleteOutline' import { Button, IconButton } from '@mui\/material' import { useRouter } from 'next\/router'  type Props = {   postId: string   authorId: string   icon?: boolean }  export default function RemovePostButton({   postId,   authorId,   icon = true }: Props) {   const router = useRouter()   const { user, accessToken } = useUser()    \/\/ \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u0435\u0433\u043e \u043f\u043e\u043b\u043d\u043e\u043c\u043e\u0447\u0438\u044f \u043d\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u0441\u0442\u0430   if (!user || user.id !== authorId) return null    const removePost = async () => {     try {       \/\/ \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0443 \u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430       await fetch(`\/api\/post?id=${postId}`, {         method: 'DELETE',         headers: {           \/\/ \u0440\u043e\u0443\u0442 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u044b\u043c           Authorization: `Bearer ${accessToken}`         }       })       \/\/ \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0431\u043b\u043e\u0433\u0430       router.push('\/posts')     } catch (e: unknown) {       console.error(e)     }   }    return icon ? (     &lt;IconButton onClick={removePost} color='error'>       &lt;DeleteOutlineIcon \/>     &lt;\/IconButton>   ) : (     &lt;Button variant='contained' color='error' onClick={removePost}>       Remove     &lt;\/Button>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u043d\u043e\u043f\u043a\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430 (<code>components\/Buttons\/EditPost.tsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { useUser } from '@\/utils\/swr' import DriveFileRenameOutlineIcon from '@mui\/icons-material\/DriveFileRenameOutline' import { Button, IconButton } from '@mui\/material' import type { Post } from '@prisma\/client' import EditPostForm from '..\/Forms\/EditPost' import Modal from '..\/Modal'  type Props = {   post: Omit&lt;Post, 'createdAt' | 'updatedAt'> &amp; {     createdAt: string   }   icon?: boolean }  export default function EditPostButton({ post, icon = true }: Props) {   const { user } = useUser()    \/\/ \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u0435\u0433\u043e \u043f\u043e\u043b\u043d\u043e\u043c\u043e\u0447\u0438\u044f \u043d\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0441\u0442\u0430   if (!user || user.id !== post.authorId) return null    return (     &lt;Modal       triggerComponent={         icon ? (           &lt;IconButton color='info'>             &lt;DriveFileRenameOutlineIcon \/>           &lt;\/IconButton>         ) : (           &lt;Button variant='contained' color='info'>             Edit           &lt;\/Button>         )       }       modalContent={&lt;EditPostForm post={post} \/>}       size='M'     \/>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u044d\u0442\u043e\u0439 \u043a\u043d\u043e\u043f\u043a\u0438 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u0441\u044f \u043c\u043e\u0434\u0430\u043b\u043a\u0430 \u0441 \u0444\u043e\u0440\u043c\u043e\u0439 \u0434\u043b\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430 (<code>components\/Forms\/EditPost.tsx<\/code>), \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u043e\u0447\u0442\u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0447\u043d\u0430 \u0444\u043e\u0440\u043c\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430.<\/p>\n<p>  <\/p>\n<p>\u041a\u043d\u043e\u043f\u043a\u0438 \u043b\u0430\u0439\u043a\u0430, \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430 \u0434\u0443\u0431\u043b\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0431\u043b\u043e\u0433\u0430 \u0432 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0430\u0445 \u043f\u0440\u0435\u0432\u044c\u044e \u043f\u043e\u0441\u0442\u043e\u0432 \u0432 \u0432\u0438\u0434\u0435 \u0438\u043a\u043e\u043d\u043e\u043a.<\/p>\n<p>  <\/p>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<\/p>\n<p>  <\/p>\n<p><em>\u0424\u043e\u0440\u043c\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430<\/em><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/kh\/gl\/qz\/khglqzuge0y6qgjb1jladbmul50.png\" data-src=\"https:\/\/habrastorage.org\/webt\/kh\/gl\/qz\/khglqzuge0y6qgjb1jladbmul50.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p><em>\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043f\u043e\u0441\u0442\u0430<\/em><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/ce\/tz\/ho\/cetzhofnjgiezdd2vl6j6h-z-r4.png\" data-src=\"https:\/\/habrastorage.org\/webt\/ce\/tz\/ho\/cetzhofnjgiezdd2vl6j6h-z-r4.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p><em>\u0424\u043e\u0440\u043c\u0430 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430<\/em><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/5n\/_z\/k4\/5n_zk4bf-rjajfc9poxotazxu9e.png\" data-src=\"https:\/\/habrastorage.org\/webt\/5n\/_z\/k4\/5n_zk4bf-rjajfc9poxotazxu9e.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p><em>\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0431\u043b\u043e\u0433\u0430<\/em><\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/ic\/hb\/wh\/ichbwhxstgfvp4pzpdjaz0moanq.png\" data-src=\"https:\/\/habrastorage.org\/webt\/ic\/hb\/wh\/ichbwhxstgfvp4pzpdjaz0moanq.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043c\u043e\u0436\u043d\u043e \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e\u0439. \u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0432\u044b \u0443\u0437\u043d\u0430\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u043d\u043e\u0432\u043e\u0435 \u0438 \u043d\u0435 \u0437\u0440\u044f \u043f\u043e\u0442\u0440\u0430\u0442\u0438\u043b\u0438 \u0432\u0440\u0435\u043c\u044f.<\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442\u0441\u044f \u043b\u044e\u0431\u044b\u0435 \u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u044f \u0438 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>  <\/p>\n<p>\u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0438 happy coding!<\/p>\n<p>  <\/p>\n<hr\/>\n<p>  <\/p>\n<p><a href=\"https:\/\/timeweb.cloud\/?utm_source=habr&amp;utm_medium=banner&amp;utm_campaign=vds-promo-6-rub\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/p-\/u9\/l2\/p-u9l27ynelxi92bcmdxhu76ma8.png\" data-src=\"https:\/\/habrastorage.org\/webt\/p-\/u9\/l2\/p-u9l27ynelxi92bcmdxhu76ma8.png\"\/><\/a><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p> <!----> <!----><\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/company\/timeweb\/blog\/715510\/\"> https:\/\/habr.com\/ru\/company\/timeweb\/blog\/715510\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/by\/f9\/qu\/byf9qu-qkjvc3cpega2osikmrcq.png\" data-src=\"https:\/\/habrastorage.org\/webt\/by\/f9\/qu\/byf9qu-qkjvc3cpega2osikmrcq.png\"\/>  <\/p>\n<p>  \u041f\u0440\u0438\u0432\u0435\u0442, \u0434\u0440\u0443\u0437\u044c\u044f!<\/p>\n<p>  <\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0435\u0440\u0438\u0438 \u0438\u0437 2 \u0441\u0442\u0430\u0442\u0435\u0439-\u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u043e\u0432 \u043c\u044b \u0441 \u0432\u0430\u043c\u0438 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u043c \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442-\u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0435 (\u0444\u0443\u043b\u043b\u0441\u0442\u0435\u043a \u2014 fullstack) \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/nextjs.org\/\">Next.js<\/a> \u0438 <a href=\"https:\/\/www.typescriptlang.org\/\">TypeScript<\/a>.<\/p>\n<p>  <\/p>\n<ul>\n<li><a href=\"https:\/\/my-js.org\/docs\/guide\/nextjs\">\u0420\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e \u043f\u043e Next.js<\/a>.<\/li>\n<li><a href=\"https:\/\/my-js.org\/docs\/guide\/ts\">\u041a\u0430\u0440\u043c\u0430\u043d\u043d\u0430\u044f \u043a\u043d\u0438\u0433\u0430 \u043f\u043e TypeScript<\/a>.<\/li>\n<\/ul>\n<p>  <\/p>\n<ol>\n<li>\u041d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u0431\u043e\u0439 \u0431\u043b\u043e\u0433 \u2014 \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u0443\u044e \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0443 \u0434\u043b\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438, \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u043e\u0432.<\/li>\n<li>\u041c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 <a href=\"https:\/\/jwt.io\/\">JSON Web Tokens<\/a> \u0438 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Cookies\">HTTP-\u043a\u0443\u043a\u0438<\/a>.<\/li>\n<li>\u0414\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0438 \u043f\u043e\u0441\u0442\u043e\u0432 \u0431\u0443\u0434\u0443\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c\u0441\u044f \u0432 \u0440\u0435\u043b\u044f\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 <a href=\"https:\/\/www.sqlite.org\/\">SQLite<\/a>.<\/li>\n<\/ol>\n<p>  <\/p>\n<p>\u0412 <a href=\"https:\/\/habr.com\/ru\/company\/timeweb\/blog\/712294\/\">\u043f\u0435\u0440\u0432\u043e\u043c \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0435<\/a> \u043c\u044b \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b\u0438 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043b\u0438 \u043f\u0440\u043e\u0435\u043a\u0442, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0440\u043e\u0443\u0442\u043e\u0432 (API Routes), \u0432\u043e \u0432\u0442\u043e\u0440\u043e\u043c \u2014 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0440\u0430\u0431\u043e\u0442\u043e\u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>  <\/p>\n<p><em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em>: \u0434\u0430\u043d\u043d\u044b\u0439 \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d \u043d\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u043c\u0435\u044e\u0442 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u043f\u044b\u0442 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 <a href=\"https:\/\/ru.reactjs.org\/\">React<\/a> \u0438 <a href=\"https:\/\/nodejs.org\/\">Node.js<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0442\u0435\u0445, \u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0434, \u0432\u043e\u0442 <a href=\"https:\/\/github.com\/harryheman\/Blog-Posts\/tree\/master\/fullstack-next-ts-app\">\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0418\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e? \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u043e\u0448\u0443 \u043f\u043e\u0434 \u043a\u0430\u0442.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-345128","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/345128","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=345128"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/345128\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=345128"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=345128"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=345128"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}