How much time do we typically spend on project setup? We're talking about configuring installed libraries and writing boilerplate code to structure and implement best practices for achieving optimal website performance. At , we often start new projects from scratch. That's why, over three years ago, we created a for the backend so that we wouldn't have to spend time developing core functionality that the end user doesn't see but is crucial for developers. Brocoders NestJS boilerplate Over this time, the boilerplate has received 1.9k stars on GitHub and has gained significant popularity beyond our company. Now, we've decided to take it a step further and created the for the front end. Its purpose is to keep our best practices in project development together, avoiding familiar pitfalls and reducing development time. Extensive React Boilerplate Modules and libraries included in the boilerplate To have server-side rendering out of the box, automatic page caching, preloading, and other speed optimizations, we use the framework. It extends React by adding static site generation and is a powerful tool for creating productive and SEO-friendly web applications. TypeScript is utilized to ensure code reliability, performance, and readability in the boilerplate. Next.js To prepare the website to support local languages and settings, we use an experienced language expert for web applications - the internationalization framework i18next. It helps organize all added language versions and adapt the content of the site, menu, and messages for different languages and regional settings. We expanded it with packages to detect the user's browser language and transform resources into the server-side of i18next. is used for quickly creating interfaces without spending time writing components from scratch, such as buttons, input fields, tables, modal windows, and more. With dark mode support, the application based on the boilerplate is automatically configured to use the user's system theme. Material-UI library is integrated for form management, providing a simple and intuitive API optimized for high performance as it works with data without unnecessary re-renders of the entire form. React Hook Form is used for state management and data caching. It automatically optimizes queries, reducing their duplication, and supports data caching on both the client and server sides, allowing easy cache management across environments. "React Query" The library provides an interface for tracking and debugging tests, supporting various types of tests, including unit tests, integration tests, user interface tests, and more. Cypress helps to ensure that the style of the code in the project is consistent with the rules already established in the .eslintrc.json file to avoid potential problems and warn about possible errors. ESLint The architecture of the react boilerplate project and folder structure The project structure allows for easy navigation and editing of various parts of the application. Automated tests are located in the folder and divided into different specifications for testing various aspects of the application. All source codes of the project, following the logical structure of the application, are concentrated in the folder. /cypress /src Nested within it, the folder displays various application pages, such as the administrative panel with pages for creating and editing users, email confirmation pages, password reset, password change, user profile, login, and registration. The folder contains common components that can be used on different pages of the application. The services section is responsible for interacting with the API server; its files contain modules that are important for proper functionality and interaction with the backend and external services. /app /components As far as this boilerplate uses the Next.js framework for building React applications, the folders are used as routes. This means the more folders you add to your app folder, the more routes you will get. Additionally, if you create a new folder inside of another folder, you will get nested routes. To better understand these concepts, we suggest looking at the image below. We use dynamic segments in routing when flexible routes are needed. Within the file structure in the folder, such routes wrap the folder name in square brackets. Thus, it is easy to guess that the variable segments in the route will be and . /app src/app/[language]/admin-panel/users/edit/[id]/ language id Mechanisms of authentication and user interaction Since the web application supports internationalization, additional middleware is added to each page to determine the language, so the language of the authentication form will be displayed depending on the basic system settings of the user's device. Sign Up page The Sign Up page contains a registration form with fields for user registration, as well as the option to register via Google and Facebook. The necessary API for requests to the server to create a new account is specified, and saving user data is implemented using a context. 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] ); } Access and refresh tokens are acquired and stored for future requests if the backend status is ok, otherwise, error handling procedures are executed. Sign In page The Sign In page contains an authentication form with fields for logging in an already registered user and, again, the option to log in via Google or Facebook. After successful authentication, the user receives an access token and a refresh token, which are stored for future requests. 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] ); Restore and update password A user may forget their password, so functionality for resetting the old password by sending a link to the email is created. Of course, for such cases, there should be a corresponding API on the server, like in our nestjs-boilerplate, which is perfect for two-way interaction. Also, there is an ability to update the password. The logic of sending an API request to the server to update the user's password and further processing its results is specified. After registering a new account on the server, a link for email confirmation must be generated. Therefore, the boilerplate has logic for the confirm-email route as well. Public and private routes Both public and private routes are implemented - the user's authorization is checked before displaying certain pages, and if the user is not authorized or the authorization data has not yet been loaded, the user is redirected to the sign-in page. Below is the HOC function that implements this logic: 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; }; } Cypress tests have been added for sign-in, sign-up, and forgot-password to detect errors and check that all the functionalities of the authentication forms work on different browsers and devices. User’s profile management The boilerplate includes user data pages and pages for editing their data. Functionality has been added to implement an avatar component that allows users to upload or change their profile photo. The page has been created to implement the ability to edit the profile, which includes a form with personal data that the user entered during registration, such as name, surname, and password, as well as adding/changing an avatar. Additionally, to ensure code quality, detect potential security issues, and verify that the profile editing functionality works properly, this part of the code is also covered by Cypress tests. /profile/edit 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("useremail@gmail.com"); 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("notexistedemail@gmail.com"); cy.getBySel("password").type("password1"); cy.getBySel("sign-in-submit").click(); cy.wait("@login"); cy.getBySel("email-error").should("be.visible"); }); }); To automate the process of detecting and updating dependencies, we use the . It helps avoid issues related to using outdated dependencies and allows controlling the dependency update process according to the project's needs. Renovate bot Conclusion We refer to the as a structured starting point for front-end development. It pairs beautifully with our for the backend, and with them, the development team can get started, minimizing setup time and focusing on developing unique aspects of the project, knowing that fundamentals are already correctly implemented. We also keep track of regular library updates and maintain the project in an up-to-date state. So, welcome to try it out :) Extensive React Boilerplate NestJS boilerplate Full credits for this article to and the main developer of the project 🇺🇦 Elena Vlasenko Vlad Shchepotin Also published . here