This is a solution to the Intro component with sign-up form. Frontend Mentor challenges help you improve your coding skills by building realistic projects.
With my solution I went a bit further and created a
- Next.js site deployed on github pages that is
- dinamically adjusts to the browser's default font size and is
- reponsive but still
- stick to the Frontend Mentor's design when the default font size is 16px.
Altough the client side input validation in this project is so basic that could be solved more simple packages like validator.js
- I used Zod package to gain experience using a more sofisticated validator library for future more complex projects written in typescript.
The unit, integration and accessibility tests increase the confidence that the site works as intended.
Desktop view:
Desktop view with errors:
Mobile view:
- Solution URL: On Frontend Mentor
- Live Site URL: On Github Pages
- Semantic HTML5 markup
- CSS custom properties
- Mobile-first workflow
- Responsive design
- Next.js + React.js
- Zod schema validation package
- Autoprefixer to increase browser coverage
- WAVE Web Accessibility Evaluation Tool
- Jest + React Testing Library + User Event Testing Library
Show the styled page on mobile devices.
Show the styled page on both desktop and mobile devices.
Refactor: Rewrite the style to use grid instead of flexbox to prevent glitches around the breakpoint.
Refactor: Factor the reused Input component and the functional SignUpForm component out into React Functional Components to enhance the maintainablilty and the testability of the page.
Show error messages on submittion attempt and prevent submittion when no or invalid data has been entered to any field so as the user be able to fix any error in the form right away.
Show error messages on lost input focus when no or invalid data has been entered to any field so as the user is notified even more earlier about any form error.
When using static fonts instead of variable ones in Next.js, you will need to specify the weights like this:
import { Poppins } from "next/font/google";
const poppinsSansSerif = Poppins({
weight: ["400", "500", "600", "700"],
variable: "--font-poppins-sans-serif",
});"Next.js can serve static files, like images, under a folder called public in the root directory. Files inside public can then be referenced by your code starting from the base URL (/)." (Next.js about public folder)
Next.js assumes the application will be served from the root of a domain (e.g. mydomain.com/) and its routing and asset handling are configured accordingly. Github Pages, however will host them from a subpath in the format <account_name>.github.io/<repository_name>/. Practically this leads to the following issues:
- ✗ - Routing: Internal links within your Next.js app will likely not work correctly as they will be looking for paths relative to the root of the domain, not the subdirectory.
- ✗ - Asset Loading: Images, CSS, and other static assets might fail to load because their paths will also be incorrect.
"To deploy a Next.js application under a sub-path of a domain you can use the basePath config option." (Next.js about basePath)
//next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
basePath: "/<repository_name>",
};
export default nextConfig;- ✔ - Routing: "When linking to other pages using
next/linkandnext/routerthebasePathwill be automatically applied." - ✗ - Asset Loading: "When using the next/image component, you will need to add the basePath in front of src."
Next.js recommendation is: "To use a local image, import your .jpg, .png, or .webp image files." So importing image like below and applying basePath will handle the image src to link under the subpath.
import Image from "next/image";
import icon from "@/public/icon-error.svg";
export default MyImage(){
return <Image src={icon} alt="Error Icon" className={styles.errorIcon} />;
}- Asset Loading:
- ✔ - For local
imported images in.js - ✗ - For images referenced by
url()in.css
- ✔ - For local
To prevent replacing the subpath manually on css url()s I inject css variables inline in the layout.js and apply them in the globals.css as follows:
//app/layout.js
import backgroundMobile from "../public/bg-intro-mobile.png";
import backgroundDesktop from "../public/bg-intro-desktop.png";
//...
export default function RootLayout({ children }) {
return (
<html lang="en">
<body
style={{
"--background-mobile": `url(${backgroundMobile.src})`,
"--background-desktop": `url(${backgroundDesktop.src})`,
}}
className={`${poppinsSansSerif.variable}`}
>
{children}
</body>
</html>
);
}/*app/globals.css*/
/*...*/
body {
background-image: var(--background-mobile);
/*...*/
}
@media (min-width: 400px) {
body {
background-image: var(--background-desktop);
/*...*/
}- Asset Loading:
- ✔ - For local
imported images in.js: "When using the next/image component, you will need to add the basePath in front of src." - ✔ - For images referenced by
url()in.css
- ✔ - For local
Note: assetPrefix replaces the basePath if any with the asset prefix of the static assets like
.cssand.jsfiles only while it does not replace those like the files (e.g. images, icons and so on) in thepublicfolder. So settingassetPrefixis not necessary in this case.
Note: The Next.js Github Action automatically replaces the next config with
output: exportandbasePath: "/<repository_name>"set. That is the reason why deploying on Github Pages work well without config modification decently except for the cssurl()reference for the background images.
Building a webpage according to a reference screenshot instead of a figma file is much harder and will probably not pixel accurate even not to the degree when working with figma files.
I found the PerfectPixel Chrome extension that overlays the reference screenshots over the rendered webpage under construction that helps visually see the differences.
When the element sizes change with the layout, using flexbox can be tricky. Iteration 3 will maybe to refactor the current solution to grid layout to prevent the undesired glitches around the breakpoint.
My intent was to test: that the Input component renders input that change its border color when get in error state with code something like this:
it("renders input that change its border color when get in error state", () => {
// Arrange and act
//
// Not in error state
render(<Input id="firstName" placeholder="First Name" />);
const inactiveInput = screen.getByLabelText("First Name");
const inactiveBorderColor =
window.getComputedStyle(inactiveInput).borderColor;
// In error state
render(<Input id="firstName" placeholder="First Name" active />);
const activeInput = screen.getByLabelText("First Name");
const activeBorderColor = window.getComputedStyle(activeInput).borderColor;
// Assert
expect(activeBorderColor).not.toBe(inactiveInput);
});This test failed because both activeBorderColor and inactiveInput result to be empty strings.
As I found out jest with jsdom does not populates the dom with style properties spcified in the css files. There are packages like jest-transform-css that intend to do the work.
Lessons learned:
- The
toHaveStyleJest DOM function can check against concrete style properties jsdomprovides thedocumentandwindowobjects that implements many browser API for testing- By default, Jest doesn't understand how to process CSS files or CSS Modules. It's primarily a JavaScript testing framework.
- CSS files or CSS modules are mocked in a default next.js environment this allows to verify that the correct class names are being applied to your HTML elements, but the actual CSS style properties defined in those files are not populated in the
jsdomenvironment. - There is a package
jest-transform-cssthat do the work. - Performance: Fully parsing and applying CSS in a unit testing environment can be resource-intensive and slow down the test suite. Mocking provides a faster and more focused approach.
Zod works properly only if noValidate is set on the form element. Otherwise, the form element catches email format error before passed to Zod. This prevented showing the error message specified.
Next.js background images:
Host Next.js project from Github pages
- Next.js and GitHub Pages, how the basePath and assetPrefix configuration options will fix your website
- Steps to deploy your Next.js app on GitHub pages
- Using GitHub Pages to Build, Deploy, and Host Next.js
- Deploying to GitHub Pages using gh-pages
Styling form and form controls


