template nextjs latest app router with mui framework, responsive desktop and mobile
3 tháng 8, 2024
Loading...
Loading...
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:
npx create-next-app@latest my-mui-nextjs-app
cd my-mui-nextjs-appChoose the following options:
src/ directory: Yesnpm install @mui/material @emotion/react @emotion/styled @mui/icons-materialsrc/theme/theme.ts:import { createTheme } from '@mui/material/styles';
const theme = createTheme({
// Customize your theme here
});
export default theme;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>
)
}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;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>
)
}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(),
},
]
}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:
src/app directory for categories, news, blog, and about.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
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;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;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>
)
}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:
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
✔ Giải mã 100 Đặc điểm của Người Thành công: Bản đồ phát triển bản thân hiệu quả
✔ Bí Kíp Kiếm Tiền Trên Facebook 2025: Hướng Dẫn Toàn Tập Tối Ưu Hóa Thu Nhập
✔ Gemini Miễn phí vs Gemini Pro (Google AI Pro): So sánh tính năng, phí và cách chọn 2025
✔ Veo 3 AI: Hướng dẫn Prompt & Chiến lược GV‑SEO 2025 cho Người Mới và Doanh Nghiệp-Phần 2
✔ Cẩm Nang Veo 3 AI 2025: Hướng Dẫn Viết Prompt Video AI Cho Marketer & Nhà Sáng Tạo
✔ Hành Trình Tự Do Tài Chính: Bí Quyết Tập Trung, Duy Trì Động Lực Và Làm Việc Hiệu Quả
✔ Các Cấp Độ Tiếng Anh và Quy Đổi Điểm TOEIC Chuẩn Xác Nhất 2025
✔ So sánh chi tiết Gemini 2.5 Pro và Gemini Flash 2.5: Hiệu năng, tính năng và ứng dụng thực tế
✔ So sánh Veo 2 và Veo 3: Hướng dẫn tạo prompt, khác biệt chi tiết và ví dụ thực tế