template nextjs latest app router with mui framework, responsive desktop and mobile
August 3rd 2024

how to create a Next.js app with the App Router using MUI (Material-UI) framework, including a responsive menu and SEO support. You can follow these steps to create the project yourself:

  1. Create a new Next.js project:
npx create-next-app@latest my-mui-nextjs-app
cd my-mui-nextjs-app

Choose the following options:

  • Use TypeScript: Yes
  • Use ESLint: Yes
  • Use Tailwind CSS: No
  • Use src/ directory: Yes
  • Use App Router: Yes
  • Customize default import alias: No
  1. Install MUI and required dependencies:
npm install @mui/material @emotion/react @emotion/styled @mui/icons-material
  1. Create a custom theme file src/theme/theme.ts:
import { createTheme } from '@mui/material/styles';

const theme = createTheme({
  // Customize your theme here
});

export default theme;
 
  1. Create a layout file src/app/layout.tsx:
import type { Metadata } from 'next'
import { AppRouterCacheProvider } from '@mui/material-nextjs/v13-appRouter';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import theme from '../theme/theme';
import Header from '../components/Header';

export const metadata: Metadata = {
  title: 'My MUI Next.js App',
  description: 'A responsive Next.js app with MUI',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <AppRouterCacheProvider>
          <ThemeProvider theme={theme}>
            <CssBaseline />
            <Header />
            {children}
          </ThemeProvider>
        </AppRouterCacheProvider>
      </body>
    </html>
  )
}
  1. Create a Header component src/components/Header.tsx:
import React, { useState } from 'react';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import MenuItem from '@mui/material/MenuItem';
import Menu from '@mui/material/Menu';
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import Link from 'next/link';

const Header = () => {
  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
  const [mobileMoreAnchorEl, setMobileMoreAnchorEl] = useState<null | HTMLElement>(null);

  const handleMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
    setAnchorEl(event.currentTarget);
  };

  const handleMobileMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
    setMobileMoreAnchorEl(event.currentTarget);
  };

  const handleMenuClose = () => {
    setAnchorEl(null);
    setMobileMoreAnchorEl(null);
  };

  const menuItems = [
    { label: 'All Categories', href: '/categories' },
    { label: 'News', href: '/news', subItems: [
      { label: 'Technology', href: '/news/technology' },
      { label: 'Business', href: '/news/business' },
    ]},
    { label: 'Blog', href: '/blog' },
    { label: 'About', href: '/about' },
  ];

  const renderMenu = (
    <Menu
      anchorEl={anchorEl}
      open={Boolean(anchorEl)}
      onClose={handleMenuClose}
    >
      {menuItems.map((item) => (
        <MenuItem key={item.label} onClick={handleMenuClose}>
          <Link href={item.href}>
            {item.label}
          </Link>
        </MenuItem>
      ))}
    </Menu>
  );

  const renderMobileMenu = (
    <Menu
      anchorEl={mobileMoreAnchorEl}
      open={Boolean(mobileMoreAnchorEl)}
      onClose={handleMenuClose}
    >
      {menuItems.map((item) => (
        <MenuItem key={item.label} onClick={handleMenuClose}>
          <Link href={item.href}>
            {item.label}
          </Link>
        </MenuItem>
      ))}
    </Menu>
  );

  return (
    <AppBar position="static">
      <Toolbar>
        <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
          My App
        </Typography>
        <Box sx={{ display: { xs: 'none', md: 'flex' } }}>
          {menuItems.map((item) => (
            <Button
              key={item.label}
              color="inherit"
              onClick={item.subItems ? handleMenuOpen : undefined}
            >
              {item.label}
            </Button>
          ))}
        </Box>
        <Box sx={{ display: { xs: 'flex', md: 'none' } }}>
          <IconButton
            size="large"
            edge="end"
            color="inherit"
            aria-label="menu"
            onClick={handleMobileMenuOpen}
          >
            <MenuIcon />
          </IconButton>
        </Box>
      </Toolbar>
      {renderMenu}
      {renderMobileMenu}
    </AppBar>
  );
};

export default Header;
  1. Update src/app/page.tsx:
import Typography from '@mui/material/Typography';
import Container from '@mui/material/Container';

export default function Home() {
  return (
    <Container maxWidth="lg">
      <Typography variant="h1" component="h1" gutterBottom>
        Welcome to My MUI Next.js App
      </Typography>
      <Typography variant="body1">
        This is a responsive Next.js app with MUI framework and App Router.
      </Typography>
    </Container>
  )
}
  1. For SEO support, create a src/app/sitemap.ts file:
import { MetadataRoute } from 'next'

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    {
      url: 'https://example.com',
      lastModified: new Date(),
    },
    {
      url: 'https://example.com/about',
      lastModified: new Date(),
    },
    {
      url: 'https://example.com/blog',
      lastModified: new Date(),
    },
  ]
}
  1. Create a src/app/robots.ts file:
import { MetadataRoute } from 'next'

export default function robots(): MetadataRoute.Robots {
  return {
    rules: {
      userAgent: '*',
      allow: '/',
      disallow: '/private/',
    },
    sitemap: 'https://example.com/sitemap.xml',
  }
}

This setup provides you with a basic Next.js 14 app using the App Router and MUI framework. It includes a responsive header with a desktop and mobile menu, and basic SEO support with metadata, sitemap, and robots.txt.

To complete your project:

  1. Create additional pages in the src/app directory for categories, news, blog, and about.
  2. Implement the dropdown functionality for the News menu item.
  3. Add more detailed SEO metadata for each page.
  4. Style your components further using MUI's theming capabilities.

Remember to replace 'https://example.com' with your actual domain in the sitemap and robots files.

Github source code: nextjs-reactjs-mui-latest

 

if your want to have drawer on the left side that's always visible on desktop and toggleable on mobile

  1. First, let's update the Header.tsx component:
// 'use client'

import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import React from 'react';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import useMediaQuery from '@mui/material/useMediaQuery';
import { useTheme } from '@mui/material/styles';

interface HeaderProps {
    onDrawerToggle: () => void;
}

const Header: React.FC<HeaderProps> = ({ onDrawerToggle }) => {
    const theme = useTheme();
    const isMobile = useMediaQuery(theme.breakpoints.down('md'));

    console.log('Header render...')
    return (
        <AppBar position="fixed" sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}>
            <Toolbar>
                {isMobile && (
                    <IconButton
                        color="inherit"
                        aria-label="open drawer"
                        edge="start"
                        onClick={onDrawerToggle}
                        sx={{ mr: 2 }}
                    >
                        <MenuIcon />
                    </IconButton>
                )}
                <Typography variant="h6" noWrap component="div">
                    My App
                </Typography>
            </Toolbar>
        </AppBar>
    );
};

export default Header;
  1. Now, let's create a new Drawer.tsx component (app/components)
import Collapse from '@mui/material/Collapse';
import Drawer from '@mui/material/Drawer';
import ExpandLess from '@mui/icons-material/ExpandLess';
import ExpandMore from '@mui/icons-material/ExpandMore';
import Link from 'next/link';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import React from 'react';
import Toolbar from '@mui/material/Toolbar';
import useMediaQuery from '@mui/material/useMediaQuery';
import { usePathname } from 'next/navigation';
import { useTheme } from '@mui/material/styles';

interface DrawerProps {
    open: boolean;
    onClose: () => void;
}

const drawerWidth = 240;

const menuItems = [
    { label: 'Home', href: '/' },
    {
        label: 'News',
        href: '/news',
        subItems: [
            { label: 'Blockchain', href: '/news/blockchain' },
            { label: 'Technical', href: '/news/technical' },
            { label: 'Programming', href: '/news/programming' },
        ]
    },
    { label: 'About', href: '/about' },
];

const DrawerContent: React.FC = () => {
    const [openSubMenu, setOpenSubMenu] = React.useState('');
    const pathname = usePathname();

    const handleSubMenuToggle = (label: string) => {
        setOpenSubMenu(openSubMenu === label ? '' : label);
    };

    return (
        <List>
            {menuItems.map((item) => (
                <React.Fragment key={item.label}>
                    <ListItem disablePadding>
                        <ListItemButton
                            component={Link}
                            href={item.href}
                            selected={pathname === item.href}
                            onClick={() => item.subItems && handleSubMenuToggle(item.label)}
                        >
                            <ListItemText primary={item.label} />
                            {item.subItems && (openSubMenu === item.label ? <ExpandLess /> : <ExpandMore />)}
                        </ListItemButton>
                    </ListItem>
                    {item.subItems && (
                        <Collapse in={openSubMenu === item.label} timeout="auto" unmountOnExit>
                            <List component="div" disablePadding>
                                {item.subItems.map((subItem) => (
                                    <ListItemButton
                                        key={subItem.label}
                                        sx={{ pl: 4 }}
                                        component={Link}
                                        href={subItem.href}
                                        selected={pathname === subItem.href}
                                    >
                                        <ListItemText primary={subItem.label} />
                                    </ListItemButton>
                                ))}
                            </List>
                        </Collapse>
                    )}
                </React.Fragment>
            ))}
        </List>
    );
};

const AppDrawer: React.FC<DrawerProps> = ({ open, onClose }) => {
    const theme = useTheme();
    const isMobile = useMediaQuery(theme.breakpoints.down('md'));

    return (
        <Drawer
            variant={isMobile ? 'temporary' : 'permanent'}
            open={isMobile ? open : true}
            onClose={onClose}
            ModalProps={{
                keepMounted: true, // Better open performance on mobile.
            }}
            sx={{
                '& .MuiDrawer-paper': {
                    boxSizing: 'border-box',
                    width: drawerWidth,
                },
            }}
        >
            <Toolbar />
            <DrawerContent />
        </Drawer>
    );
};

export default AppDrawer;
  1. Update the layout.tsx file to include the drawer:
'use client'

import AppDrawer from '../components/Drawer';
import { AppRouterCacheProvider } from '@mui/material-nextjs/v14-appRouter';
import Box from '@mui/material/Box';
import CssBaseline from '@mui/material/CssBaseline';
import Header from '../components/Header';
import { ThemeProvider } from '@mui/material/styles';
import theme from '../theme/theme';
import { useState } from 'react';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  const [mobileOpen, setMobileOpen] = useState(false);

  const handleDrawerToggle = () => {
    setMobileOpen(!mobileOpen);
  };

  return (
    <html lang="en">
      <body>
        <AppRouterCacheProvider>
          <ThemeProvider theme={theme}>
            <CssBaseline />
            <Box sx={{ display: 'flex' }}>
              <Header onDrawerToggle={handleDrawerToggle} />
              <AppDrawer open={mobileOpen} onClose={handleDrawerToggle} />
              <Box
                component="main"
                sx={{ flexGrow: 1, p: 3, width: { sm: `calc(100% - 240px)` } }}
              >
                <ThemeProvider theme={theme}>
                  <CssBaseline />
                  <Box component="main" sx={{ flexGrow: 1, p: 3 }}>
                    {children}
                  </Box>
                </ThemeProvider>
              </Box>
            </Box>
          </ThemeProvider>
        </AppRouterCacheProvider>
      </body>
    </html>
  )
}
  1. Update the page.tsx file to add some padding at the top:
import Box from '@mui/material/Box';
import Container from '@mui/material/Container';
import Typography from '@mui/material/Typography';

export default function Home() {
  return (
    <Box sx={{ mt: 8 }}>
      <Container maxWidth="lg">
        <Typography variant="h1" component="h1" gutterBottom>
          Welcome to My MUI Next.js App
        </Typography>
        <Typography variant="body1">
          This is a responsive Next.js app with MUI framework and App Router.
        </Typography>
      </Container>
    </Box>
  )
}

These changes will:

  1. Always show the drawer on desktop screens.
  2. Make the drawer toggleable on mobile screens.
  3. Update the menu structure to include News with submenu items for Blockchain, Technical, and Programming.
  4. Highlight the active link based on the current route.
  5. Ensure the content doesn't overlap with the app bar.

Remember to create the corresponding pages for each route in your src/app directory (e.g., src/app/news/blockchain/page.tsx, src/app/news/technical/page.tsx, etc.).

This setup provides a responsive layout with a left-side drawer that adapts to desktop and mobile views, includes nested menu items, and highlights the active link. The SEO support from the previous example is still in place.

 

Github source code: on branch Drawer_Mobile_toggleMenu

 

Huy Le
Huy Le
[email protected]

Full-stack developer passionate about React and react native, firebase

Tags
nextjs
mui
template
seo
app router
responsive
Social Share
Loading...