
Welcome to ZenLogic Labs! Today, we’re diving into the art of architecting a high-performance front-end application using Next.js. This guide will explore essential features like static site generation (SSG), caching, lazy loading, and more, making it a perfect resource for technical leaders and experienced developers looking to optimize their front-end frameworks.
In this blog, we’ll go through a real-world scenario where we’ll build an analytics dashboard for a large e-commerce platform. We’ll highlight how Next.js and a few powerful libraries can create an app that’s fast, SEO-friendly, and capable of handling heavy data loads. Ready to dig in? Let’s go!
Scenario Overview
The project we’re working on is a data-intensive analytics dashboard. This dashboard will serve a high-traffic e-commerce site with robust performance and SEO requirements. Users need quick access to various data-intensive dashboards, so we’ll leverage Next.js to handle tasks like SSG, caching, and lazy-loading seamlessly.
Key Tools and Libraries
Here’s the tech stack we’ll use to build our application:
- Next.js: For its server-side rendering (SSR) and SSG capabilities.
- React: Our go-to UI library.
- Apollo Client: For managing data from our GraphQL API.
- Chakra UI: A flexible, modular component library for styling.
- SWR: For data fetching and caching.
- TypeScript: To improve type safety and the developer experience.
Structuring the App
A clear file structure is key to maintainability. Here’s a clean setup to organize components, hooks, pages, services, and styles.
my-nextjs-app/
├── components/
│ ├── Header.tsx
│ ├── Footer.tsx
│ └── Dashboard/
│ ├── DashboardHeader.tsx
│ └── DashboardContent.tsx
├── hooks/
│ └── useDashboardData.ts
├── pages/
│ ├── index.tsx
│ ├── about.tsx
│ └── dashboards/
│ └── [id].tsx
├── services/
│ └── graphql.ts
├── styles/
│ └── globals.css
├── tests/
│ ├── components/
│ │ ├── Header.test.tsx
│ │ ├── Footer.test.tsx
│ │ └── Dashboard/
│ │ ├── DashboardHeader.test.tsx
│ │ └── DashboardContent.test.tsx
│ └── hooks/
│ └── useDashboardData.test.ts
4. Setting Up Next.js
To get started, create a new Next.js project and initialize TypeScript:
npx create-next-app my-nextjs-app –typescript
cd my-nextjs-app
Adding Chakra UI for Styling
Chakra UI is a versatile component library that simplifies styling. Install it with:
npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion
Wrap the app with ChakraProvider in _app.tsx to apply the theme globally: typescript Copy code
// pages/_app.tsx
import { AppProps } from 'next/app';
import { ChakraProvider } from '@chakra-ui/react';
import '../styles/globals.css';
function MyApp({ Component, pageProps }: AppProps) {
return (
<ChakraProvider>
<Component {...pageProps} />
</ChakraProvider>
);
}
export default MyApp;
Connecting to GraphQL with Apollo Client
To connect to our GraphQL backend, install Apollo Client:
npm install @apollo/client graphql
Create a graphql.ts
file to configure Apollo:
// services/graphql.ts
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://your-graphql-endpoint.com/graphql',
cache: new InMemoryCache(),
});
export default client;
Data Fetching with SWR
SWR makes data fetching and caching easy. Install it with:
npm install swr
Create a simple fetch utility:
// utils/fetcher.ts
export const fetcher = (url: string) => fetch(url).then((res) => res.json());
Implementing Static Site Generation
SSG helps Next.js pre-render pages for performance and SEO. Here’s how we can create a dynamic dashboard page:
// pages/dashboards/[id].tsx
import { GetStaticPaths, GetStaticProps } from 'next';
import { gql } from '@apollo/client';
import client from '../../services/graphql';
import useSWR from 'swr';
import { fetcher } from '../../utils/fetcher';
import { Box, Spinner } from '@chakra-ui/react';
const Dashboard = ({ initialData }) => {
const { data } = useSWR(`/api/dashboards/${id}`, fetcher, { initialData });
if (!data) return <Spinner />;
return (
<Box>
<h1>{data.dashboard.title}</h1>
</Box>
);
};
export const getStaticPaths: GetStaticPaths = async () => {
const { data } = await client.query({
query: gql`query { dashboards { id } }`,
});
const paths = data.dashboards.map(dashboard => ({
params: { id: dashboard.id.toString() },
}));
return { paths, fallback: true };
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
const { data } = await client.query({
query: gql`query($id: ID!) { dashboard(id: $id) { id title } }`,
variables: { id: params.id },
});
return {
props: {
initialData: data,
},
revalidate: 10,
};
};
export default Dashboard;
9. Creating a Custom Hook for Reusability
By moving the dashboard data-fetching logic into a custom hook, we achieve reusability and cleaner components.
// hooks/useDashboardData.ts
import { useRouter } from 'next/router';
import useSWR from 'swr';
import { fetcher } from '../utils/fetcher';
export const useDashboardData = (initialData) => {
const router = useRouter();
const { data, error } = useSWR(`/api/dashboards/${id}`, fetcher, { initialData });
return {
data,
error,
isLoading: !error && !data,
};
};
Setting Up Unit Testing
Testing enhances code reliability and enables confident refactoring. Here’s how to set up Jest for unit testing:
Install Jest and supporting libraries:
npm install --save-dev jest @testing-library/react @testing-library/jest-dom babel-jest @babel/preset-env @babel/preset-react @babel/preset-typescript
Configure Babel and Jest:
// .babelrc
{
"presets": ["next/babel", "@babel/preset-typescript"]
}
// jest.config.js
const nextJest = require('next/jest');
const createJestConfig = nextJest({ dir: './' });
module.exports = createJestConfig({
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapper: { '^@/components/(.*)$': '<rootDir>/components/$1' },
testEnvironment: 'jest-environment-jsdom',
});
Writing a Test
Once configured, start writing tests for core components. For example, here’s a test for our Header
component:
// tests/components/Header.test.tsx
import { render, screen } from '@testing-library/react';
import Header from '../../components/Header';
describe('Header', () => {
it('renders the header', () => {
render(<Header />);
const headerElement = screen.getByText(/Header/i);
expect(headerElement).toBeInTheDocument();
});
});
Run your tests with:
Run your tests with:
npm test
Wrapping Up
And that’s it! We’ve built a high-performance Next.js app ready to handle real-world data and performance demands. This architecture ensures scalability, reusability, and a smooth development experience. Here at ZenLogic Labs, we believe that a well-architected front-end is the foundation of a reliable user experience, especially for data-heavy applications.
Stay tuned for more insights into front-end development best practices, and feel free to reach out if you’d like to learn more about building powerful applications with Next.js.