Blog app with Nextjs (app router), React, MUI framework
July 19th 2024
Setting up Firebase Firestore
First, ensure you have set up Firebase in your Next.js project. Install the necessary dependencies:
npm install firebase
npm install @mui/material @emotion/react @emotion/styled
Create a firebase.js file to initialize Firebase:
// firebase.js
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_STORAGE_BUCKET",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID",
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
export { db };
Creating the Blog Component
Create a Blog component to display individual blog posts:
// components/Blog.js
import React from "react";
import { Card, CardContent, Typography, Chip } from "@mui/material";
const Blog = ({ blog }) => {
return (
<Card>
<CardContent>
<Typography variant="h5" component="div">
{blog.title}
</Typography>
<Typography variant="body2" color="text.secondary">
{blog.content}
</Typography>
<div>
{blog.tags.map((tag) => (
<Chip key={tag} label={tag} />
))}
</div>
</CardContent>
</Card>
);
};
export default Blog;
Fetching Blogs with Pagination and Filtering
Create a utility function to fetch blogs from Firestore:
// utils/fetchBlogs.js
import { collection, query, where, orderBy, limit, startAfter, getDocs } from "firebase/firestore";
import { db } from "../firebase";
export const fetchBlogs = async (lastVisible = null, tag = null, pageSize = 5) => {
let q = collection(db, "blogs");
if (tag) {
q = query(q, where("tags", "array-contains", tag));
}
q = query(q, orderBy("createdAt"), limit(pageSize));
if (lastVisible) {
q = query(q, startAfter(lastVisible));
}
const snapshot = await getDocs(q);
const blogs = snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
const lastVisibleDoc = snapshot.docs[snapshot.docs.length - 1];
return { blogs, lastVisible: lastVisibleDoc };
};
Creating the Main Blog Page
Create the main blog page to display the list of blogs with pagination and tag filtering:
// pages/blogs/index.js
import React, { useState, useEffect } from "react";
import { fetchBlogs } from "../../utils/fetchBlogs";
import Blog from "../../components/Blog";
import { Button, Container, Grid, TextField, Typography } from "@mui/material";
const BlogsPage = () => {
const [blogs, setBlogs] = useState([]);
const [lastVisible, setLastVisible] = useState(null);
const [tag, setTag] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
const loadBlogs = async () => {
setLoading(true);
const { blogs, lastVisible } = await fetchBlogs();
setBlogs(blogs);
setLastVisible(lastVisible);
setLoading(false);
};
loadBlogs();
}, []);
const loadMoreBlogs = async () => {
setLoading(true);
const { blogs: newBlogs, lastVisible: newLastVisible } = await fetchBlogs(lastVisible, tag);
setBlogs([...blogs, ...newBlogs]);
setLastVisible(newLastVisible);
setLoading(false);
};
const handleFilterChange = (e) => {
setTag(e.target.value);
};
const handleFilterApply = async () => {
setLoading(true);
const { blogs, lastVisible } = await fetchBlogs(null, tag);
setBlogs(blogs);
setLastVisible(lastVisible);
setLoading(false);
};
return (
<Container>
<Typography variant="h4" gutterBottom>
Blogs
</Typography>
<TextField
label="Filter by tag"
variant="outlined"
value={tag}
onChange={handleFilterChange}
fullWidth
margin="normal"
/>
<Button variant="contained" color="primary" onClick={handleFilterApply} disabled={loading}>
Apply Filter
</Button>
<Grid container spacing={2}>
{blogs.map((blog) => (
<Grid item xs={12} sm={6} md={4} key={blog.id}>
<Blog blog={blog} />
</Grid>
))}
</Grid>
{lastVisible && (
<Button variant="contained" color="secondary" onClick={loadMoreBlogs} disabled={loading}>
Load More
</Button>
)}
</Container>
);
};
export default BlogsPage;
Adding Firestore Data Security Rules
Ensure you have appropriate security rules in Firestore to allow read access:
service cloud.firestore {
match /databases/{database}/documents {
match /blogs/{blog} {
allow read: if true;
}
}
}
Conclusion
This code provides a basic implementation of a blog app with Next.js, Firebase Firestore, and Material-UI. The main features include fetching blogs, filtering by tags, and supporting pagination. Make sure to adjust the Firebase configuration and security rules according to your project’s needs.