Extensive React Boilerplate як доповнення до Brocoders Nest.js Boilerplate для створення вебзастосунків
Скільки часу зазвичай ми витрачаємо на запуск проєкту? Йдеться зараз про узгодження інстальованих бібліотек і написання бойлерплейт-коду для оформлення структури та впровадження best practices для отримання гарного перформансу сайту. Ми в компанії Brocoders досить часто починаємо новий проєкт з нуля. Тому більше трьох років тому ми створили NestJS boilerplate для бекенду, щоб кожного разу не витрачати час на розробку основної функціональності, яку не бачить кінцевий користувач, але яка має вирішальне значення для розробників. За цей час бойлерплейт отримав майже 2K зірочок на GitHub і має великий попит за межами нашої компанії. Тепер ми вирішили піти далі й створили для фронтенду Extensive React Boilerplate. Його мета — тримати наші найкращі напрацювання з розробки проєктів разом, обходячи вже знайомі граблі і скорочення часу розробки.
Огляд модулів і бібліотек
Щоб мати з коробки серверний рендеринг, автоматичне збереження кешу сторінок, попереднє завантаження та інші оптимізації швидкості, використовуємо фреймворк Next.js. Він розширює React, додаючи статичну генерацію сайту, і є потужним інструментом для створення продуктивних і дружніх до SEO вебзастосунків. Для забезпечення більшої надійності коду, продуктивності та читабельності в бойлерплейті використаний TypeScript. З метою підготовки вебсайту до підтримки місцевих мов та налаштувань, використаний досвічений мовний експерт для вебзастосунків — internationalization-framework i18next. Він допомагає впорядкувати всі додані мовні версії й адаптувати вміст сайту, меню та повідомлення для різних мов та регіональних налаштувань. Розширили його пакетами для визначення мови через браузер користувача і для трансформування ресурсів в серверну частину i18next. Material-UI використаний для можливості швидко створювати інтерфейси, не витрачаючи час на написання компонентів з нуля, як-от кнопки, поля вводу, таблиці, модальні вікна тощо. Завдяки підтримці dark mode, застосунок на основі бойлерплейту автоматично налаштований на використання системної теми користувача. Для управління формами інтегрована бібліотека React Hook Form, що надає простий та інтуїтивно зрозумілий API. Вона оптимізована для високої продуктивності, оскільки працює з даними без зайвих перерендерів всієї форми. Для управління станом та кешування даних використано React Query. Вона автоматично виконує оптимізацію запитів, зменшуючи їх дублювання, а також підтримує кешування даних як на клієнтському, так і на серверному боці, дозволяючи легко керувати кешем в різних середовищах. Інтерфейс для відстежування та налагодження тестів надає бібліотека Cypress, що підтримує різні типи тестів, зокрема модульні тести, інтеграційні тести, тести з користувацьким інтерфейсом та інші. ESLint допомагає забезпечити однорідність стилю коду в проєкті, у файлі .eslintrc.json вже встановлені правила, що допомагають уникнути потенційних проблем та попереджають про можливі помилки.
Архітектура і структура папок
Структура проєкту дозволяє легко знаходити та редагувати різні частини застосунку. У папці /cypress розташовані автоматизовані тести, розділені на різні специфікації для тестування різних аспектів застосунку. А весь вихідний код проєкту, відповідно до логічної структури застосунку, зосереджено в папці /src. У вкладеній в неї папці /app відображені різні сторінки застосунку, як-от адміністративна панель зі сторінками для створення і редагування юзерів, сторінки підтвердження електронної пошти, відновлення паролю, зміни паролю, профіль користувача, вхід та реєстрація. У папці /components розміщені загальні компоненти, які можуть бути використані на різних сторінках застосунку. Розділ /services відповідає за взаємодію з API-сервером, її файли містять модулі, що є важливими для правильної функціональності і взаємодії з бекендом та зовнішніми сервісами.
Візуальне пояснення утворення роутів для проєкту Оскільки наш бойлерплейт використовує фреймворк Next.js, папки використовуються як маршрути. Це означає, що більше папок ви додасте до app, то більше маршрутів ви отримаєте. Крім того, якщо створити нову папку всередині іншої папки, ви отримаєте вкладені маршрути. Щоб краще зрозуміти ці поняття, ми пропонуємо подивитися на зображення нижче.
Візуальне пояснення утворення роутів для проєкту Ми використовуємо динамічні сегменти в роутингу, коли потрібні гнучкі маршрути. Всередині файлової структури у папці /app такі роути огортують назву папки квадратними дужками. Так нескладно здогадатися, що змінними сегментами в маршруті src/app/[language]/admin-panel/users/edit/[id]/ будуть language та id.
Механізми аутентифікації та взаємодії з користувачем
Оскільки вебзастосунок підтримує інтернаціоналізацію, до кожної сторінки додані мідлвари для визначення мови, тож користувачу мова форми авторизації відображатиметься залежно від базових системних налаштувань його девайсу.
Сторінка Sign Up
Сторінка Sign Up містить форму реєстрації з полями для реєстрації користувача, а також можливість зареєструватися через гугл і фейсбук. Прописаний API, необхідний для запитів на сервер для створення нового облікового запису, а збереження даних користувача реалізовано за допомогою контексту. Кастомний хук для аутентифікації через гугл:
export function useAuthGoogleLoginService() { const fetchBase = useFetchBase(); return useCallback( (data: AuthGoogleLoginRequest) => { return fetchBase(`${API_URL}/v1/auth/google/login`, { method: "POST", body: JSON.stringify(data), }).then(wrapperFetchJsonResponse<AuthGoogleLoginResponse>); }, [fetchBase] ); }
Кастомний хук для аутентифікації через фейсбук:
export function useAuthFacebookLoginService() { const fetchBase = useFetchBase(); return useCallback( (data: AuthFacebookLoginRequest, requestConfig?: RequestConfigType) => { return fetchBase(`${API_URL}/v1/auth/facebook/login`, { method: "POST", body: JSON.stringify(data), ...requestConfig, }).then(wrapperFetchJsonResponse<AuthFacebookLoginResponse>); }, [fetchBase] ); }
Залежно від статусу, отриманого з бекенду, або отримується токен доступу та refresh token, які зберігаються для подальших запитів, або здійснюється обробка помилок.
Сторінка Sign In
Сторінка Sign In містить форму авторизації з полями для входу вже зареєстрованого користувача, і, знову ж таки, можливість зайти через через гугл чи фейсбук. Після успішної аутентифікації користувача отримується токен доступу та refresh-токен, які зберігаються для подальших запитів. Збереження токену в cookies внаслідок успішної аутентифікації:
if (status === HTTP_CODES_ENUM.OK) { setTokensInfo({ token: data.token, refreshToken: data.refreshToken, tokenExpires: data.tokenExpires, }); setUser(data.user); }
const setTokensInfo = useCallback( (tokensInfo: TokensInfo) => { setTokensInfoRef(tokensInfo); if (tokensInfo) { Cookies.set(AUTH_TOKEN_KEY, JSON.stringify(tokensInfo)); } else { Cookies.remove(AUTH_TOKEN_KEY); setUser(null); } }, [setTokensInfoRef] );
Функції відновлення та зміни паролю
Користувач може забути пароль, тож створена функціональність для скидання старого паролю за допомогою відправлення лінки на Email. Звісно, для таких випадків має бути і відповідне API на сервері, як, наприклад, у нашому бойлерплейті nestjs-boilerplate, який ідеально підходить для двосторонньої взаємодії. Створена можливість оновлення пароля. Прописана логіка API-запиту відправки на сервер даних для оновлення пароля користувача і подальшої обробки її результатів. Після реєстрації нового облікового запису на сервері має згенеруватися посилання для підтвердження Email. Тож у бойлерплейті є логіка і для роуту confirm-email.
Публічні і приватні роути
Реалізовані публічні й приватні роути — здійснюється перевірка авторизації користувача перед відображенням певних сторінок, і якщо користувач не авторизований або дані про авторизацію ще не завантажені, відбувається перенаправлення користувача на сторінку sign-in. Нижче наведемо HOC-функцію, що реалізує дану логіку:
function withPageRequiredAuth( Component: FunctionComponent<PropsType>, options?: OptionsType ) { // … return function WithPageRequiredAuth(props: PropsType) { // … useEffect(() => { const check = () => { if ( (user && user?.role?.id && optionRoles.includes(user?.role.id)) || !isLoaded ) return; const currentLocation = window.location.toString(); const returnToPath = currentLocation.replace(new URL(currentLocation).origin, "") || `/${language}`; const params = new URLSearchParams({ returnTo: returnToPath, }); let redirectTo = `/${language}/sign-in?${params.toString()}`; if (user) { redirectTo = `/${language}`; } router.replace(redirectTo); }; check(); }, [user, isLoaded, router, language]); return user && user?.role?.id && optionRoles.includes(user?.role.id) ? ( <Component {...props} /> ) : null; }; }
Для виявлення помилок та перевірки, що всі функціональності форм аутентифікації працюють на різних браузерах і пристроях, для sign-in, sign-up та forgot-password додані написані на Cypress тести.
Керування профілем користувача
Бойлерплейт містить сторінки даних користувача та їхнє редагування. Додана функціональність, яка реалізує компонент аватара, що дозволяє користувачам завантажити або змінити фотографію профілю.
Створена сторінка /profile/edit з реалізацією можливості редагувати профіль, що містить форму з особистими даними, які користувач ввів під час реєстрації, як-от імʼя, прізвище, пароль, а також додавання/зміну аватару. Також для забезпечення якості коду, виявлення потенційних проблем безпеки та перевірки, що функціональність редагування профілю працює належно, ця частина коду також покрита тестами на Cypress. Тест для перевірки відображення помилок при валідації форми на сторінці sign-in:
describe("Validation and error messages", () => { beforeEach(() => { cy.visit("/sign-in"); }); it("Error messages should be displayed if required fields are empty", () => { cy.getBySel("sign-in-submit").click(); cy.getBySel("email-error").should("be.visible"); cy.getBySel("password-error").should("be.visible"); cy.getBySel("email").type("[email protected]"); cy.getBySel("email-error").should("not.exist"); cy.getBySel("sign-in-submit").click(); cy.getBySel("password-error").should("be.visible"); cy.getBySel("password").type("password1"); cy.getBySel("password-error").should("not.exist"); cy.getBySel("email").clear(); cy.getBySel("email-error").should("be.visible"); }); it("Error message should be displayed if email isn't registered in the system", () => { cy.intercept("POST", "/api/v1/auth/email/login").as("login"); cy.getBySel("email").type("[email protected]"); cy.getBySel("password").type("password1"); cy.getBySel("sign-in-submit").click(); cy.wait("@login"); cy.getBySel("email-error").should("be.visible"); }); });
Щоб автоматизувати процес виявлення та оновлення залежностей, ми використовуємо Renovate bot. Він допомагає уникнути проблем, пов’язаних з використанням застарілих версій залежностей та дозволяє контролювати процес оновлення залежностей згідно з потребами проєкту.
Підсумок
Ми відносимось до Extensive React Boilerplate як до структурованої відправної точки під час розробки фронтенду. Він чудово поєднується з нашим NestJS boilerplate для бекенду, і з його допомогою команда розробників може почати роботу, мінімізуювавши час налаштування та зосередившись на розробці унікальних аспектів проєкту, знаючи, що фундаментальні елементи вже правильно реалізовано. Також ми стежимо за регулярним оновленням бібліотек і підтримуємо проєкт в актуальному стані. Будемо вдячні за ваші зірочки і коментарі :-)
Дякую за цей матеріал Владу Щепотину і Олені Власенко 🇺🇦
2 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів