Suman Sourabh's Blog
August 26, 2025
Build an FAQ Page on Next.js with Material UI
This post will mention how you can create a simple but good looking FAQ section or a page on your “Next.js” application with “Material UI” design library.
What is an FAQ section?An FAQ (Frequently Asked Question) section is something which includes a bunch of popular questions and answers that the users may have about a product or an organization.
Almost all of the websites have this section. It is extremely helpful for the users as all the questions are at one place.
Popular Examples of FAQ


Open the terminal in VS Code or a text editor of your choice and run the following command:
npx create-next-app@latestThis will create a barebones Next.js app on your local system.
2. Install Material UIRun the following command.
For npm
npm install @mui/material @emotion/react @emotion/styledFor yarn
yarn add @mui/material @emotion/react @emotion/styled3. Install Material UI IconsWe will need this to display the dropdown icon for a component which we will use for the FAQ. To install this, run the following command.
For npm
npm install @mui/icons-materialFor yarn
yarn add install @mui/icons-material4. Create FAQ Component, but how?Normally, FAQs are created by using a component where the answer of a question is hidden and when the user clicks on the question, the answer gets displayed on the screen.
If the user clicks on the question again, the answer gets hidden.
Which component to use for this?
5. How to use Accordion?“Accordion” is the answer.
A simple code for an MUI accordion component can be this:
expandIcon={}
aria-controls="panel1-content"
id="panel1-header"
>
Accordion 1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
malesuada lacus ex, sit amet blandit leo lobortis eget.
Paste the above code on any of your Next.js pages.
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";const FrequentlyAskedQuestions = () => {
return (
expandIcon={}
aria-controls="panel1-content"
id="panel1-header"
>
Accordion 1
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
malesuada lacus ex, sit amet blandit leo lobortis eget.
);
};
In the above code, I have created a FrequentlyAskedQuestions page.
You can change the content of this page easily by fetching the data from a locally stored JSON file or from a server.
Let’s discuss about fetching data from a JSON file in the next step.
6. Customize it (Optional)Create a JSON file and name it something like “sample.json” and paste the content below:
[{
"question": "What is Libertas?",
"answer": "Libertas is an online discussion platform where users can post content, comment, and vote on other users' posts. All for FREE. No premium subscription."
},
{
"question": "How do I create an account on Libertas?",
"answer": "You can create an account by visiting the Libertas homepage and clicking on the 'Sign Up' button or by clicking on the 'Sign Up' button at the top navigation bar. You can sign up using your email address."
},
{
"question": "How is this different from Reddit?",
"answer": "There is no need for any karma points or something. You can get started just by signing up! Plus, you won't get banned or restricted without any reason. If you do get banned (which we hope not), we will tell you the reason."
},
{
"question": "Is there any minimum points required to post on Libertas?",
"answer": "No. There are no points required for you to post and interact on Libertas."
},
{
"question": "Who moderates Libertas?",
"answer": "There are no moderators. You have the liberty to engage on Libertas."
},
{
"question": "Can I report a post on Libertas?",
"answer": "Yes, we want Libertas to be a safe and fun place for everyone. You can report a post by clicking on the 'report post' button below a post. We will review it as soon as possible."
},
{
"question": "How can I search for content on Libertas?",
"answer": "You can search for content using the search bar at the top of the Libertas website. Use keywords or phrases related to the topic you are interested in."
}
]
The above code is the content that I have used in the FAQ page on Libertas, an online discussion platform.
Fetch the data by importing the json file on the FAQ page.
import faq from "@/sample.json";Use map function and wrap it inside an MUI Grid component.
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";const FrequentlyAskedQuestions = () => {
useEffect(() => {
document.title = "Frequently Asked Questions | Libertas";
}, []);
return (
container
gap={2}
sx={{ px: 8, mb: 20, maxWidth: "1000px", mx: "auto" }}
>
{faq.map((question, index) => (
expandIcon={}
aria-controls="panel-content"
id="panel-header"
>
{question.question}
{question.answer}
))}
);
};7. Outcome

Hope now you can create an FAQ section on your Next.js app. Try to use inspiration from popular FAQ pages to enhance the overall look and feel.
But don’t forget about the user experience!
Try Libertas here
Contact me on X (Twitter) or LinkedIn to ask me any question about this
The post Build an FAQ Page on Next.js with Material UI appeared first on Suman Sourabh.
August 17, 2025
How To Build a Document Viewer in Next.js
This quick guide will show you how to display the preview of a document using react-documents package in a Next.js application.
Steps1. Create a Next.js projectOpen a text editor of your choice (I use VS Code) and type the following command in the terminal:
npx create-next-app@latestFollow the instructions and a new Next.js app will be created.
You can run the app with npm run dev and go to http://localhost:3000 to see your application.


Install the react-documents package with this command:
npm i react-documentsYou will see the following line in your package.json file.
"react-documents": "^1.2.1"3. Add the code for document previewCopy the code below into the page.js file present in this path: app\page.js
Do not save just yet as it will throw a runtime error.

To prevent this error, paste the code below on the top of your page.js file:
"use client";Now, code will look like this:

Upon save, this is how your project will look like on the browser.

This is because we did not add the location or the URL of the document, so nothing can be previewed.
4. Add a document URLYou can use this PDF URL:
https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf
Now, add the code below to the DocumentViewer component
url="https://www.w3.org/WAI/ER/tests/xhtml..."Now, code will be like this:

Save the project and check the browser. The PDF will be displayed on the previewer.

You can enhance the look and feel of the page by adding a few styles.
ConclusionThis guide talked about how to display a document viewer in a Next.js app.
The post How To Build a Document Viewer in Next.js appeared first on Suman Sourabh.
August 11, 2025
How I Deployed Node.js/Express App to Vercel (Painfully)
Below is how much tries I had to perform until I finally deployed Recite, my Node.js/Express app on Vercel successfully.

Let’s see what all steps I performed.
You can deploy mostly, any Node.js/Express app on Vercel with this method.
Pre-requisitesNode.js/Express app, a GitHub repository, and a Vercel account.
Initial State of the AppThis is the initial project file structure of my Express app.

And the index.js file:
const express = require("express");const dotenv = require("dotenv");
const quoteRoute = require("./routes/QuoteRoute");
const cors = require("cors");
const connectDb = require("./utils/connectDb");
const errorHandler = require("./middlewares/errorHandler");
const notFound = require("./middlewares/notFound");
const apiRequestLimiter = require("./middlewares/rateLimit");
const app = express();
dotenv.config();
connectDb();
app.use(express.json());
app.use(cors({ origin: "*" }));
app.use(apiRequestLimiter);
app.use("/api/v1", quoteRoute);
app.use(notFound);
app.use(errorHandler);
const port = process.env.PORT;
app.listen(port, () => {
console.log(`listening on http://localhost:${port} `);
});
Every route of my app is through “/api/v1”.
Steps to Deploy to Vercel 1. Export “index.js” app as a handlerOn Vercel, we can’t “listen” like in a normal express server.
app.listen(port, () => {console.log(`listening on http://localhost:${port} `);
});
We need to export the app as a handler.
module.exports = app;Final index.js code looks like this:
const express = require("express");const dotenv = require("dotenv");
const quoteRoute = require("../routes/QuoteRoute");
const cors = require("cors");
const connectDb = require("../utils/connectDb");
const errorHandler = require("../middlewares/errorHandler");
const notFound = require("../middlewares/notFound");
const apiRequestLimiter = require("../middlewares/rateLimit");
const app = express();
dotenv.config();
connectDb();
app.use(express.json());
app.use(cors({ origin: "*" }));
app.use(apiRequestLimiter);
app.use("/api/v1", quoteRoute);
app.use(notFound);
app.use(errorHandler);
const port = process.env.PORT;
module.exports = app;2. Create “Vercel.json” file in the root directory

And copy paste the code below:
{"version": 2,
"builds": [
{
"src": "index.js",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "index.js"
}
]
}
This vercel.json file tells us if we want to use a custom build for our app.
Breakdown1. Specifying the version of our app:
"version": 22. Telling Vercel to build index.js as a Node.js serverless function using Vercel’s Node runtime.
"builds": [{
"src": "index.js",
"use": "@vercel/node"
}
]
3. The below code tells Vercel that any request that comes to “/” endpoint, should be handled by the Express app at index.js.
"routes": [{
"src": "/(.*)",
"dest": "index.js"
}
]But right now, there’s a mistake in our project.
If we don’t create an “api” folder on the root directory, and don’t paste the index.js file there, Vercel wouldn’t treat it as a serverless function and none of the routes in our app will work.
So we need to make 2 changes:
1. Locate index.js inside the “api” folder in root directory:

2. Update vercel.json to reflect the new “api” folder change:
{"version": 2,
"builds": [
{
"src": "api/index.js",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "api/index.js"
}
]
}3. Deploy the app to Vercel
You must have a GitHub repo for this.
Login to vercel.com and import the GitHub repo, then simply deploy it. You will see the below status.

And if we go to the URL, we will see the below page.

Because of notFound handler that we have in our code on index.js file.
app.use(notFound);Because if you see the index.js file, we don’t have any route for the home endpoint (“/”), so the Express app points us to the notFound handler, which has this code:
const notFound = (req, res, next) => {const error = new Error("Resource not found");
error.status = 404;
return next(error);
};
module.exports = notFound;Fix
On index.js file, I can simply create a new route for the home endpoint.
app.get("/", (req, res) => {res.json({ success: true, message: "Recite - Quotes API is running..." });
});
Now if you hit this URL: https://recite-flax.vercel.app/api/v1/random
You will see that the app is working!

You can use this Recite REST API in your application to get any random quote!
Errors:1. Misconfiguration in your ‘vercel.json’ fileSometimes, you may get this error on your Vercel log:

Because most likely, vercel.json would have some incorrect configuration.
{"version": 2,
"builds": [
{
"src": "index.js",
"use": "vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "index.js"
}
]
}
We need to include “@” on the use property’s value:
{"version": 2,
"builds": [
{
"src": "index.js",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "index.js"
}
]
}Verdict
Hope you learnt how to deploy a Node.js/Express app to Vercel using the above steps.
Thankfully, Vercel’s serverless concept is quite faster than Render’s free tier.

Hence, I moved my REST API to Vercel.
I am also using this API on Vocabsaga, a vocabulary learning platform where you can learn new words through passages and sentences, not random single words.
Contact me on X (Twitter) or LinkedIn for any queries.
The post How I Deployed Node.js/Express App to Vercel (Painfully) appeared first on Suman Sourabh.
August 8, 2025
How I Created a Password Reset Flow in Next.js
One of the most annoying experiences for a user is to forget their password.
To make it even more aggravating, some reset password flows are so badly designed that the user would just think of not using the application again.
Therefore, it is really crucial to have a solid UX for the password reset process. This is one of the problems that I encountered while building Libertas, an online discussion platform, built with Next.js.
Hence, I surfed on the internet and got inspired by this article.
Below is how I created a password reset flow in Next.js and how you can do it too.
But first…
How a Password Reset Flow should look like?
Ideally, the password reset flow should have 5 steps that are pretty straightforward. They are listed below:
1. “Forgot password?” link




For the final step, some would argue that why not just get the user automatically signed in after confirming their new password. But maybe this is largely avoided just to add an additional layer of security.
Steps to implement password reset flow in Next.js1. Create a Next.js appType the following command:
npx create-next-app@latest2. Create a Login page
Code:
"use client";import ErrorText from "@/components/errorComponents/ErrorText";
import LoadingButton from "@/components/pageComponents/LoadingButton";
import { GlobalContext } from "@/services/globalContext";
import { Button, Stack } from "@mui/material";
import Link from "next/link";
import { useContext, useEffect, useState } from "react";
import TextInput from "../../components/formComponents/TextInput";
import { useRouter } from "next/navigation";
import { colors } from "@/theme/colors";
import PasswordInput from "@/components/formComponents/PasswordInput";
import TitleText from "@/components/pageComponents/TitleText";
const Login = () => {
const { loading, login, loginError, user } = useContext(GlobalContext);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const router = useRouter();
const submitHandler = (e) => {
e.preventDefault();
// console.log(email, password);
login(email, password);
};
useEffect(() => {
if (user) {
router.push("/feed");
}
}, [user]);
useEffect(() => {
document.title = `Login | Libertas`;
}, []);
return (
style={{ display: "flex", justifyContent: "center", padding: "4rem 0" }}
>
{loginError && }
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required={true}
/>
password={password}
handlePassword={(e) => setPassword(e.target.value)}
showCapsLockOnMessage={true}
/>
href="https://sumansourabh.in/recover-password"
style={{
fontSize: "0.875rem",
color: "#000",
textDecoration: "underline",
}}
>
Forgot password?
variant="contained"
type="submit"
style={{
textTransform: "capitalize",
backgroundColor: colors.button.background,
fontWeight: "600",
borderRadius: "0rem",
marginTop: 30,
}}
>
Login
{loading && (
)}
Don't have an account?{" "}
href="https://sumansourabh.in/sign-up"
style={{ color: "#000", textDecoration: "underline" }}
>
Sign up
);
};
export default Login;
TextInput component:
import { TextField } from "@mui/material";import React from "react";
const TextInput = ({
type,
placeholder,
value,
onChange,
required,
nameOfInput,
}) => {
return (
type={type}
label={placeholder}
value={value}
onChange={onChange}
fullWidth
required={required ? required : false}
size="small"
name={nameOfInput}
/>
);
};
export default TextInput;
PasswordInput component:
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Visibility, VisibilityOff } from "@mui/icons-material";
import { IconButton, InputAdornment, Stack, TextField } from "@mui/material";
import { useState } from "react";
const PasswordInput = ({ password, handlePassword, showCapsLockOnMessage }) => {
const [showPassword, setShowPassword] = useState(false);
const [capsLockOnMessage, setCapsLockOnMessage] = useState("");
const handleClickShowPassword = () => {
setShowPassword(!showPassword);
};
// detect if caps lock is on
const handleKeyUp = (e) => {
const capsLockOn = e.getModifierState("CapsLock");
if (capsLockOn) {
setCapsLockOnMessage("Caps Lock is on");
} else {
setCapsLockOnMessage("");
}
};
return (
size="small"
type={showPassword ? "text" : "password"}
label="Password"
value={password}
onChange={handlePassword}
required={true}
InputProps={{
endAdornment: (
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
edge="end"
>
{showPassword ? : }
),
}}
fullWidth
onKeyUp={handleKeyUp}
/>
{showCapsLockOnMessage && !!capsLockOnMessage && (
{capsLockOnMessage}
)}
);
};
export default PasswordInput;3. Add the “Forgot password?” link
Then an email will pop up on the user’s Gmail account.
href="https://sumansourabh.in/recover-password"style={{
fontSize: "0.875rem",
color: "#000",
textDecoration: "underline",
}}
>
Forgot password?
I have used a component of Next.js, which on clicking, goes to “/recover-password” page.


Code:
"use client";import ErrorText from "@/components/errorComponents/ErrorText";
import TextInput from "@/components/formComponents/TextInput";
import LoadingButton from "@/components/pageComponents/LoadingButton";
import TitleText from "@/components/pageComponents/TitleText";
import { GlobalContext } from "@/services/globalContext";
import { colors } from "@/theme/colors";
import { Button, Stack } from "@mui/material";
import { useContext, useEffect, useState } from "react";
const RecoverPassword = () => {
const { loading, passwordRecoveryEmailError, sendPasswordRecoveryEmail } =
useContext(GlobalContext);
const [emailOrUsername, setEmailOrUsername] = useState("");
const submitHandler = (e) => {
e.preventDefault();
sendPasswordRecoveryEmail(emailOrUsername);
};
const isButtonDisabled = !emailOrUsername;
return (
style={{ display: "flex", justifyContent: "center", padding: "4rem 0" }}
>
title="Recover Password"
text="It's okay, we have got this!"
/>
{passwordRecoveryEmailError && (
)}
type="text"
placeholder="Email or username"
value={emailOrUsername}
onChange={(e) => setEmailOrUsername(e.target.value)}
required={true}
/>
variant="contained"
type="submit"
style={{
textTransform: "none",
backgroundColor: colors.button.background,
fontWeight: "600",
borderRadius: "0rem",
}}
sx={{
"&.Mui-disabled": {
color: "grey",
},
}}
disabled={isButtonDisabled}
>
Send me a reset password email
{loading && (
)}
);
};
export default RecoverPassword;
sendPasswordRecoveryEmail function is in the next step.
5. Send the “Change Password” mail to the userTo send the email to the user, I used nodemailer package in my Node.js/Express backend.
a. Install nodemailer
With npm
npm i nodemailerWith yarn
yarn add nodemailerb. Setup nodemailer
I have used “Gmail” as the service, so before creating the controller to send password recovery email to the user in their Gmail account, I had to setup nodemailer with the help of this page.
c. Create the controller to send email to the user
const sendPasswordRecoveryEmail = asyncHandler(async (req, res, next) => {const { emailOrUsername } = req.body;
if (!emailOrUsername) {
res.status(400);
return next(new Error("Field is required"));
}
// Find the user
let userAvailable;
if (emailOrUsername.includes("@")) {
userAvailable = await UserModel.findOne({ email: emailOrUsername });
} else {
userAvailable = await UserModel.findOne({ username: emailOrUsername });
}
if (!userAvailable) {
res.status(400);
return next(new Error("User is not found"));
}
const html = `
Hi, ${userAvailable.name},
Here's your password recovery link
Reset password here
Best regards, Libertas
`;
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.GOOGLE_ACCOUNT_USER,
pass: process.env.GOOGLE_ACCOUNT_PASS,
},
});
if (userAvailable) {
// sending email with nodemailer
const info = await transporter.sendMail({
from: '"Libertas" ', // sender address
to: userAvailable.email,
subject: `Reset your Libertas password`, // Subject line
html: html, // html body
});
res.status(201).json({
success: true,
message: "Password recovery email has been sent succesfully",
id: userAvailable._id,
email: userAvailable.email,
info: info,
});
} else {
res.status(400);
return next(new Error("Something went wrong!"));
}
});
In the above code:
I am allowing the user to type either their “username” or “email”.
const { emailOrUsername } = req.body;I am checking if the user with that email or username exists in the database. If not, I throw an error.// Find the userlet userAvailable;
if (emailOrUsername.includes("@")) {
userAvailable = await UserModel.findOne({ email: emailOrUsername });
} else {
userAvailable = await UserModel.findOne({ username: emailOrUsername });
}
if (!userAvailable) {
res.status(400);
return next(new Error("User is not found"));
}I have created a simple html markup that will show on the email that goes into the user’s Gmail account.const html = `
Hi, ${userAvailable.name},
Here's your password recovery link
Reset password here
Best regards, Libertas
`;Next, I have configured the nodemailer transporter. I have specificed “gmail” as the “service” and used my Google username and password which was setup with this.const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.GOOGLE_ACCOUNT_USER,
pass: process.env.GOOGLE_ACCOUNT_PASS,
},
});jIf the user is available in the database, I send the mail to their Gmail account.if (userAvailable) {
// sending email with nodemailer
const info = await transporter.sendMail({
from: '"Libertas" ', // sender address
to: userAvailable.email,
subject: `Reset your Libertas password`, // Subject line
html: html, // html body
});
res.status(201).json({
success: true,
message: "Password recovery email has been sent succesfully",
id: userAvailable._id,
email: userAvailable.email,
info: info,
});
} else {
res.status(400);
return next(new Error("Something went wrong!"));
}
About half of the code above is not necessary as typing only email will work for most users, but in Libertas, I gave the option to find the user by both their “username” and “email”.
d. The mail in action
If nodemailer works successfully, an email will pop up on the user’s Gmail account.

It is a good practice to let the user know about what happened when they clicked on the “Send me a password recovery email” button.
This way they won’t be left hanging.

Code for above “feedback” page is:
"use client";import { Stack } from "@mui/material";
import Link from "next/link";
import { useEffect } from "react";
const PasswordRecoveryEmailSent = () => {
return (
style={{ display: "flex", justifyContent: "center", padding: "4rem 0" }}
>
alignItems="center"
spacing={2}
style={{ marginBottom: "2rem", textAlign: "center" }}
>
Check your email
An email has been sent to your email address to reset your password.
href="https://sumansourabh.in/login"
style={{
fontSize: "0.875rem",
color: "#000",
textDecoration: "underline",
}}
>
Back to login
);
};
export default PasswordRecoveryEmailSent;6. Create a “Reset Password” page
When the user clicks on the “Reset password here” link on the email, I redirect them to the the “Reset password” page where they can create a new password.

Code:
"use client";import ErrorText from "@/components/errorComponents/ErrorText";
import TextInput from "@/components/formComponents/TextInput";
import LoadingButton from "@/components/pageComponents/LoadingButton";
import TitleText from "@/components/pageComponents/TitleText";
import { GlobalContext } from "@/services/globalContext";
import { colors } from "@/theme/colors";
import { Button, Stack } from "@mui/material";
import { useContext, useEffect, useState } from "react";
const ResetPassword = ({ params }) => {
const [user, setUser] = useState(null);
const [password, setPassword] = useState("");
const [password2, setPassword2] = useState("");
const { loading, getSpecificUser, resetUserPassword, passwordResetError } =
useContext(GlobalContext);
useEffect(() => {
let mounted = true;
const fetchUser = async () => {
const data = await getSpecificUser(params.emailOrUsername);
if (data?.data?.success) {
setUser(data?.data?.user);
}
};
fetchUser();
return () => (mounted = false);
}, []);
const submitHandler = (e) => {
e.preventDefault();
resetUserPassword(user?._id, password, password2);
};
const isButtonDisabled = password.length > 0 && password2.length > 0;
return (
style={{ display: "flex", justifyContent: "center", padding: "4rem 0" }}
>
{passwordResetError && }
type="password"
placeholder="New password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required={true}
/>
type="password"
placeholder="Confirm password"
value={password2}
onChange={(e) => setPassword2(e.target.value)}
required={true}
/>
variant="contained"
type="submit"
style={{
textTransform: "none",
backgroundColor: colors.button.background,
fontWeight: "600",
borderRadius: "0rem",
}}
sx={{
"&.Mui-disabled": {
color: "grey",
},
}}
disabled={!isButtonDisabled}
>
Change Password
{loading && (
)}
);
};
export default ResetPassword;
The above code contains a form that runs a submitHandler function that further calls the resetUserPassword function that resets the user’s password.
7. Create controller to reset the password on the backendI created a resetPassword controller to read both the password and confirmPassword values, compare both and update the user details with the new password.
const resetPassword = asyncHandler(async (req, res, next) => {const { id, password, confirmPassword } = req.body;
if (!password || !confirmPassword) {
res.status(400);
return next(new Error("Both password fields are required"));
}
// Find the user
if (password !== confirmPassword) {
res.status(400);
return next(new Error("Passwords do not match"));
}
const hashedPassword = await bcrypt.hash(password, 10);
const updateUser = await UserModel.findByIdAndUpdate(
id,
{
password: hashedPassword,
},
{
new: true,
}
);
if (updateUser) {
res.status(201).json({
success: true,
message: "Password has been reset succesfully",
user: updateUser._id,
});
} else {
res.status(400);
return next(new Error("Something went wrong!"));
}
});
I have takes the above controller and used in this PUT route:
router.put("/reset-password", resetPassword);Then called the above route on the frontend.
Once the new password is validated, user will see the page below:

Code for the above page:
"use client";import { Stack } from "@mui/material";
import Link from "next/link";
import { useEffect } from "react";
const ResetPasswordSuccess = () => {
return (
style={{ display: "flex", justifyContent: "center", padding: "4rem 0" }}
>
alignItems="center"
spacing={2}
style={{ marginBottom: "2rem", textAlign: "center" }}
>
Password successfully reset!
Let's go! Your password has been changed successfully!
href="https://sumansourabh.in/login"
style={{
fontSize: "0.875rem",
color: "#000",
textDecoration: "underline",
}}
>
Login
);
};
export default ResetPasswordSuccess;
Here, you have a two choices:
Automatically redirect the user to the “Login” page.Leave it upto the user about the next steps.8. AuthenticateFinal step of the reset password flow is to login with the new password.

That’s it! Was it too much? No worries, take it slow, step-by-step.
ConclusionThis post discussed how I created a password reset flow on Libertas. You can use the above process in your React/Next.js application too!
If you need any help, you can contact me on LinkedIn and Twitter. I usually try to reply fast.
The post How I Created a Password Reset Flow in Next.js appeared first on Suman Sourabh.
August 5, 2025
How I Convert PDF to Markdown
I have used multiple online tools to convert PDF documents to Markdown format, but none of them came close to Marker.
Along with basic Markdown conversion, it formats tables, converts most equations to latex, extracts and stores images.
Here’s how I use Marker to extract PDF content and convert them into valid Markdown.
Environment UsedWindows 11
PrerequisitesAs per Marker’s GitHub repository, it requires the installation of:
PythonPyTorch
Go to Python Downloads page and download the latest version of Python.

Install the setup by following the instructions.
2. Install PyTorchNote: For PyTorch to be installed correctly, you must have Python 3.8 or higher installed on your system.
To install PyTorch, go to its official website and you will something like the image below:

You can tweak those options to see which one would fit best for your system. Once you have your command, open PowerShell or Command Prompt and paste your command there.
Here’s the command that I used to install PyTorch:
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118PyTorch will begin installing on your system…

It will take some time to download and install as the main file amounts to a size of 2.7 GB.
After a few minutes, PyTorch will be installed.

Now, the prerequisites are over. Next, you can go ahead and move to the actual Marker stuff.
Clone MarkerYou can clone Marker project on your local system with the following command:
git clone https://github.com/VikParuchuri/marke...After cloning, the Marker GitHub repo would look something like this:

We have cloned the repository but still we cannot convert the PDFs into Markdown format as we haven’t installed Marker.
Steps to Install Marker1. Create new environmentOutside of the newly cloned Marker GitHub repository, create a new environment for converting PDF to Markdown files.
python -m venv myenvThis will create a myenv folder consisting of multiple files.

This will activate the the newly created environment.

This command will actually install marker-pdf with the pip package manager.
pip install marker-pdfNow we are ready to convert PDF documents to Markdown files!
4. Convert PDF format to MarkdownTo convert a PDF to Markdown, we need two things:
Input path of the PDFOutput pathBecause the command for the conversion is something like this:
marker_single "input_path" "output_path" - batch_multiplier 2 - max_pages 12Hence, inside the cloned marker GitHub project folder, I will create two folders:
pdfs: My input folderoutput: My output folderAnd I will use a sample PDF for the Markdown conversion and paste it inside the pdfs folder.

Now, to convert the PDF “Get_Started_With_Smallpdf.pdf”, I will use the following command:
marker_single "D:/projects/marker-pdf/marker/pdfs/Get_Started_With_Smallpdf.pdf" "D:/projects/marker-pdf/marker/output" - batch_multiplier 2 - max_pages 12
Here’s what the other two arguments mean according to Marker GitHub repo :
--batch_multiplier is how much to multiply default batch sizes by if you have extra VRAM. Higher numbers will take more VRAM, but process faster. Set to 2 by default. The default batch sizes will take ~3GB of VRAM.
--max_pages is the maximum number of pages to process. Omit this to convert the entire document.
Once the command is executed, Marker will initiate the conversion and save the Markdown to the output folder.

The cool thing about Marker is that it extracts all the images associated with the PDF and stores it along with the main .md (Markdown) file.


Awesome! We have converted the PDF into Markdown. But wait!! How’s the output in Markdown look?
PDF InputHere’s the PDF that we supplied Marker as the input file

Ready to take document management to the next level?

## Digital Documents—All In One Place
With the new Smallpdf experience, you can
 freely upload, organize, and share digital documents. When you enable the 'Storage' option, we'll also store all processed files here.
## Enhance Documents In One Click
When you right-click on a file, we'll present
 you with an array of options to convert, compress, or modify it.
## Access Files Anytime, Anywhere
You can access files stored on Smallpdf from

your computer, phone, or tablet. We'll also sync files from the Smallpdf Mobile App to our online portal
## Collaborate With Others
Forget mundane administrative tasks. With Smallpdf, you can request e-signatures, send large files, or even enable the Smallpdf G Suite App for your entire organization.
Pretty good, right?
ConclusionIn this tutorial, we used Marker to extract the content of a PDF and convert it into Markdown format.
Of course, the PDF had only one page, but Marker is capable of a lot many pages and it does a fine job with that!
You can try and play with it yourself!
The post How I Convert PDF to Markdown appeared first on Suman Sourabh.
August 1, 2025
Display a PDF in Next.js Using React PDF Viewer
This blog talks about how you can display a PDF file in your Next.js app.
Prerequisites1. Create a Next.js project with the following commandnpx create-next-app@latestFollow the instructions and a new Next.js project will be created.
2. Run the Next.js projectType the following command on the terminal.
npm run devOpen a web browser like Chrome, and go to http://localhost:3000. You will see your new Next.js project running.

Now, we will use the React PDF viewer component to display the PDF file on the page.
You can refer this documentation to get started: Getting started — React PDF Viewer (react-pdf-viewer.dev)
Steps to use React PDF Viewer:1. Install a few packagesnpm install pdfjs-dist@3.4.120npm install @react-pdf-viewer/core@3.12.02. Copy the original code
Go to Basic usage — React PDF Viewer (react-pdf-viewer.dev) and paste the code on page.js inside the project.
import { Viewer, Worker } from "@react-pdf-viewer/core";export default function Home() {
return (
);
}3. Run the project
Most likely, you will encounter an error.

This is a pretty common error in Next.js about the usage of client components.
Basically, it just says that React PDF Viewer component uses some code that can only work on client components and not the server components.
4. Fix the server errorWhat you have to do is just type “use client” on top of your page.
Now, the overall code becomes this

Now you will encounter another error.

This error comes when there is a mismatch in the versions of the worker and the pdfjs-dist package.
To fix this, just change the version of pdfjs-dist inside the workerUrl to 3.4.120.
If you run now, you will see the PDF being displayed on the page.

But hold on! There’s something weird.
Look at the right side of the page, there’s a black background which looks odd.
6. Copy stylesTo make the styling correct, copy and paste the styles from this page: starter/nextjs at main · react-pdf-viewer/starter (github.com)
// Import the styles provided by the react-pdf-viewer packagesimport "@react-pdf-viewer/default-layout/lib/styles/index.css";
import "@react-pdf-viewer/core/lib/styles/index.css";
Now, you will encounter another error:
Module not found: Error: Can't resolve '@react-pdf-viewer/default-layout/lib/styles/index.css'To resolve this, install this package:
npm install @react-pdf-viewer/default-layoutNow, final dependencies are

Upon running the project now, the PDF will be displayed on the full page.

import { Viewer, Worker } from "@react-pdf-viewer/core";
import "@react-pdf-viewer/default-layout/lib/styles/index.css";
import "@react-pdf-viewer/core/lib/styles/index.css";
export default function Home() {
return (
);
}Conclusion
In this blog post, we saw how you can display a PDF previewer on a page in Nextjs application.
The post Display a PDF in Next.js Using React PDF Viewer appeared first on Suman Sourabh.
July 30, 2025
Ultimate Guide to Capture Screenshots with Node.js
I am building Frontera, a web application where I need to showcase a lot of landing pages/home pages of various websites and software products.
So I needed screenshots. A lot of screenshots!
I found some APIs but almost none were free. Even if they were, I didn’t want to pay, as currently I am just focused on creating the MVP.
So, let’s cut the chat.
Here’s how you can build your own Node.js/Express screenshot project where screenshots will be taken once you run your code!
Just like that.
Prerequisites1. Create a Node.js projectnpm init -y2. Install ExpressWe will build our server using Express, a Node.js framework. You must have used this or seen it
3. Create index.js file for the server codeNote: We will also install nodemon package to automatically run our project without the need of closing and restarting the server.
Create an index.js file in the root directory. Paste the code below:
const express = require("express");const PORT = process.env.PORT || 3011;
const app = express();
app.listen(PORT, () => {
console.log(`listening to port: ${PORT}`);
});
And make the following changes to your package.json file:
{"name": "screenshot-nodejs",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"express": "^5.1.0",
"nodemon": "^3.1.10",
}
}4. Install Puppeteer
The most essential thing in our project!
Using Puppeteer, we will take the screenshots of the landing pages by opening them in a new browser automatically.
npm install puppeteerBefore that…
Thought processAs a start, I will have an array of objects where each object will contain:{ name: "linear", url: "https://linear.app" }I will loop through this array and with puppeteer, open each URL and then take a screenshot.Next, I will save each screenshot in a folder in my root directory.Repeat the process for every object in the array.Full Code:const puppeteer = require('puppeteer');const fs = require('fs');
const path = require('path');
// List of sites you want to screenshot
const sites = [
{ name: 'nike', url: 'https://www.nike.com' },
{ name: 'paypal', url: 'https://www.paypal.com' },
{ name: 'google', url: 'https://www.google.com' },
{ name: 'lovable', url: 'https://www.lovable.dev' }
];
// Ensure screenshots folder exists
const outputDir = path.join(__dirname, 'screenshots');
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir);
(async () => {
const browser = await puppeteer.launch({
headless: 'new',
defaultViewport: {
width: 1200,
height: 1600,
},
});
const page = await browser.newPage();
for (const site of sites) {
try {
console.log(`

await page.goto(site.url, {
waitUntil: 'networkidle2',
timeout: 60000,
});
const filename = `${site.name}.jpg`;
const filePath = path.join(outputDir, filename);
await page.screenshot({
path: filePath,
fullPage: true,
type: 'jpeg',
quality: 80,
});
console.log(`

} catch (err) {
console.error(`

}
}
await browser.close();
})();What happened in the above code?I imported the required packages.const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');sites array: contains all the sites that I need the screenshot of, you can add more.// List of sites you want to screenshot
const sites = [
{ name: 'nike', url: 'https://www.nike.com' },
{ name: 'paypal', url: 'https://www.paypal.com' },
{ name: 'google', url: 'https://www.google.com' },
{ name: 'lovable', url: 'https://www.lovable.dev' }
];I ensured that the screenshots folder exists in the root directory. If it doesn’t exists, create it.// Ensure screenshots folder exists
const outputDir = path.join(__dirname, 'screenshots');
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir);
Now comes the Puppeteer code.
I launch the browser in a headless mode (no UI) with a window size of 1200px x 1600px, means screenshot of this viewport size will be taken.const browser = await puppeteer.launch({headless: 'new',
defaultViewport: {
width: 1200,
height: 1600,
},
});Of course, you can alter the sizes and see which one works best for your use case


// ....I tell Puppeteer to go to the specific URL, wait for the content and all its assets (images, videos, fonts, scripts) to mostly load.await page.goto(site.url, {
waitUntil: 'networkidle2',
timeout: 60000,
});
For the above, I have kept the timeout limit to 60 seconds.
This means, for each page, if the request to open the site URL on a new page takes more than 60 seconds, it will fail to take a screenshot and we will move on to the next site.
Next, I assign a name to the screenshot that will be taken by Puppeteer and the path where it will store the image.const filename = `${site.name}.jpg`;const filePath = path.join(outputDir, filename);Finally, I take the screenshot with the screenshot() method.await page.screenshot({
path: filePath,
type: 'jpeg',
quality: 80,
});
Above, if you want to take the screenshot of the full screen you can add one more key value pair:
await page.screenshot({path: filePath,
fullPage: true, // add this to take full page screenshots
type: "jpeg",
quality: 80,
});After the screenshot operation and repeating it for all the sites in the array, I closed the browser.await browser.close();Output
Your screenshots will get stored in the screenshots folder.
And you will get something like this as the output:



Pretty cool, isn’t it?
Errors encounteredDevelopment without errors? Haha, this isn’t utopia, my friend!
1. Name not resolvedFaced this error a few times, was able to resolve it after just restarting the server with npm run dev

Common error of session timed out.

Fixed this by increasing the timeout limit to 120000 ms.
await page.goto(site.url, {waitUntil: "networkidle2",
timeout: 120000,
});3. Failed to make a stable connection
Sometimes, Puppeteer could fail to continue the connection before even taking the screenshot.

Implemented a bunch of changes for this in code:
Open new page for each site instead of a single page for everything.Change the waitUntil property to domcontentloaded.Handled anti-bot mechanism as some sites might disable headless browsers.Improved Codeconst express = require("express");const puppeteer = require("puppeteer");
const fs = require("fs");
const path = require("path");
const PORT = process.env.PORT || 3011;
const app = express();
const sites = require("./data/sites"); // array of 'sites' coming from sites.js
// Ensure screenshots folder exists
const outputDir = path.join(__dirname, "screenshots");
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir);
(async () => {
const browser = await puppeteer.launch({
headless: "new",
defaultViewport: { width: 1200, height: 1600 },
args: [
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
], // Optimize for stability
});
for (const site of sites) {
let page;
try {
const filename = `${site.name}.jpg`;
const filePath = path.join(outputDir, filename);
if (fs.existsSync(filePath)) {
console.log(`! Screenshot already exists for ${site.url}: ${filePath}`);
continue;
}
console.log(`

page = await browser.newPage();
// Set a realistic user agent to avoid bot detection
await page.setUserAgent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
);
// Navigate with a longer timeout and lenient wait condition
await page.goto(site.url, {
waitUntil: "domcontentloaded", // Use 'domcontentloaded' for faster loading
timeout: 120000, // Increase timeout to 2 minutes
});
// Wait for additional time to ensure dynamic content loads
await new Promise((resolve) => setTimeout(resolve, 5000));
await page.screenshot({
path: filePath,
fullPage: false,
type: "jpeg",
quality: 80,
});
console.log(`

} catch (err) {
console.error(`

// Optionally, add retry logic here
} finally {
// Close the page to free resources
if (page) await page.close();
// Add a small delay between captures to avoid overwhelming the browser
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
await browser.close();
console.log("

})();
app.listen(PORT, () => {
console.log(`listening to port: ${PORT}`);
});
In the above code, also added a check to see if the image already exists, don’t take a screenshot again for the same site. This will save some resources and time, of course.
const filename = `${site.name}.jpg`;const filePath = path.join(outputDir, filename);
if (fs.existsSync(filePath)) {
console.log(`! Screenshot already exists for ${site.url}: ${filePath}`);
continue;
}
This will print on the log if the image exists.



Some of the screenshots didn’t output correctly. It might be that those sites took more time to load as the assets could be of more size or there could be use of heavy animations.



Now you have it. There’s your own Node.js code that can take screenshots of any website!
Next stepsCompress/resize images with sharp libraryStore the images to AWS S3/Cloudinary/Supabase/Firebase, etc instead of storing on your server itselfConvert this into an API and start charging for it lolThe post Ultimate Guide to Capture Screenshots with Node.js appeared first on Suman Sourabh.
February 21, 2025
ChatGPT vs Gemini — From a Frontend Developer’s POV
Here, I do a quick comparison between arguably the two most used Generative AI tools, OpenAI ChatGPT and Google Gemini, as a Frontend Developer with 2 yrs+ experience.
CriteriaDSA (Easy level)UI component creationExtract data from image and convert to HTMLLet’s start then.
1. DSA ProblemSometimes, frontend developers get asked DSA questions too during interviews, so let’s give an easy problem of determining if a number is a palindrome or not.
A palindrome number is a number that reads the same forward and backward. In other words, if you reverse the digits of the number, it remains unchanged.
Example: 121 → reversed is 121 (same) hence, a palindrome
Prompt: “Write a JavaScript program to determine if a number is a palindrome or not. Write it using a while loop.”
ChatGPT
Right off the bat, GPT 4 Turbo provides the response in an additional canvas view, which is kind of cool.
It outputs the program with quite a bunch of helpful comments and it lists down 4 test cases for the isPalindrome function invocation.
Gemini
On the other hand, 2.0 Flash of Gemini gives me the program, which is more or less same as the one given by GPT, but it doesn’t have the comments listed there except for one.
But what it does offer is that it gives an explanation of the whole program below it in plain text. Also, it lists down 7 test cases, 3 more than ChatGPT, especially with “0” as an argument.
DifferencesChatGPTGeminiGood and ample amount of commentsVery few commentsNo code explanationVast code explanation2. Build a UI ComponentFrontend developers cannot live without the good old UI components.
It is often helpful to just ask these GenAI tools to generate UI components so that we can have a good boilerplate code to start with. And then we can build on top of that.
Here in my Next.js application, I want to display a lot of cars on the page and the data is coming from an API.
Prompt:
ChatGPTGive me a responsive grid layout with Next.js, Tailwind and shadcn/ui that displays cars.
Each car item should display the car’s:
ImageBrandModelPrice

Here’s the full code ChatGPT provided me with:
"use client";import Image from "next/image";
import { Card, CardContent } from "@/components/ui/card";
const cars = [
{
id: 1,
image: "/cars/tesla.jpg",
brand: "Tesla",
model: "Model S",
price: "$80,000",
},
{
id: 2,
image: "/cars/bmw.jpg",
brand: "BMW",
model: "M4",
price: "$75,000",
},
{
id: 3,
image: "/cars/audi.jpg",
brand: "Audi",
model: "RS5",
price: "$85,000",
},
{
id: 4,
image: "/cars/mercedes.jpg",
brand: "Mercedes",
model: "AMG GT",
price: "$120,000",
},
];
export default function CarGrid() {
return (
Available Cars
{cars.map((car) => (
src={car.image}
alt={`${car.brand} ${car.model}`}
width={400}
height={250}
className="w-full h-48 object-cover"
/>
{car.brand}
{car.model}
{car.price}
))}
);
}
It’s a good thing that it even gave the data of the cars, so that we can have a nice template for our reference. Later we can just alter it a bit to include data coming from any other source.
But I only need the grid layout, so I will just copy this code into my project.
{cars.map((car) => (
src={car.image}
alt={`${car.brand} ${car.model}`}
width={400}
height={250}
className="w-full h-48 object-cover"
/>
{car.brand}
{car.model}
{car.price}
))}
And if I just update the values of the below attributes according to my API response:
key={car._id}src={car.img.url}{car.price.ex_showroom}I also changed the Next.js component to the native HTML [image error] tag.Here’s the output that I get:

Quite cool, right?
Although the color selection for the price is debatable but it is a kind of a simple but good looking UI.
It is responsive as well:

Final code:
{cars.map((car) => (
[image error] src={car.img.url}
alt={`${car.brand} ${car.model}`}
width={400}
height={250}
className="w-full h-48 object-cover"
/>
{car.brand}
{car.model}
{car.price.ex_showroom}
))}
Gemini

Here’s the code Gemini provided me with:
// components/CarGrid.jsimport Image from 'next/image';
import { Card, CardHeader, CardBody } from "@/components/ui/card"
const cars = [
{
image: '/images/car1.jpg', // Replace with your image paths
brand: 'Toyota',
model: 'Camry',
price: '$25,000',
},
{
image: '/images/car2.jpg',
brand: 'Honda',
model: 'Civic',
price: '$22,000',
},
{
image: '/images/car3.jpg',
brand: 'Tesla',
model: 'Model 3',
price: '$40,000',
},
// ... more car data
{
image: '/images/car4.jpg', // Replace with your image paths
brand: 'BMW',
model: 'M5',
price: '$80,000',
},
{
image: '/images/car5.jpg',
brand: 'Mercedes-Benz',
model: 'C-Class',
price: '$50,000',
},
{
image: '/images/car6.jpg',
brand: 'Audi',
model: 'A4',
price: '$45,000',
},
];
const CarGrid = () => {
return (
{cars.map((car, index) => (
{/* Added rounded corners and shadow */}
src={car.image}
alt={`${car.brand} ${car.model}`}
width={500} // Adjust width as needed
height={300} // Adjust height as needed
className="object-cover rounded-t-lg" // Rounded top corners for image
/>
{car.brand} {car.model}
{car.price}
))}
);
};
export default CarGrid;
Here too, I need the grid layout, so I will just take this code:
{cars.map((car, index) => (
{/* Added rounded corners and shadow */}
src={car.image}
alt={`${car.brand} ${car.model}`}
width={500} // Adjust width as needed
height={300} // Adjust height as needed
className="object-cover rounded-t-lg" // Rounded top corners for image
/>
{car.brand} {car.model}
{car.price}
))}
I will update the values of the below attributes according to my API response:
src={car.img.url}{car.price.ex_showroom}Changed the Next.js component to the native HTML [image error] tag.Output

Responsiveness

Final code
{cars.map((car, index) => (
{" "}
{/* Added rounded corners and shadow */}
[image error] src={car.img.url}
alt={`${car.brand} ${car.model}`}
width={500} // Adjust width as needed
height={300} // Adjust height as needed
className="object-cover rounded-t-lg" // Rounded top corners for image
/>
{car.brand} {car.model}
{car.price.ex_showroom}
))}
Issues Faced
Faced an issue with Gemini’s output.
It had a component inside , which is not defined in shadcn/ui, so it gave me an error.

It should have been instead.
VerdictBoth the outputs were good looking UIs and were responsive as well. They provided the code by including the Next.js optimized component, which is quite good.
Notice that there’s a major difference between both the responses.
ChatGPT didn’t provide any padding on the image, so the images on Creta and Verna are touching the sides of the their cards, which doesn’t look good.

Whereas Gemini’s response did include the padding but it didn’t have the perfect card alignment. The image size isn’t proper, so the car’s name and price do not align to the bottom of the card.

Gemini’s response also had an incorrect component inside the of shadcn/ui, but the UI still looks good because
Color selection for car price was good and easy on the eyes instead of the bright green color.{car.price.ex_showroom}
What do you think?
3. Data Extraction from an ImageOnce, I was given a task to extract the data from an image and convert it into an HTML table. So I turned to ChatGPT and Gemini for this.
Let’s pick an image that we want to convert to an HTML table.

The above asset is an image instead of an HTML table, so it is not in a text format and hence, we cannot copy any text inside a cell in this table.
We will let the tools handle this.
Prompt: “Extract data from this image and convert it into an HTML table”
ChatGPT
Couldn’t create a link for this as ChatGPT doesn’t support sharing links that contain images in the prompts
Here’s the code it provided:
Region
Manager
Coordinator
Local Rep
Secretary
North
Adam
Beth
Callum
Diane
South West
Annie
Dodie
Dylan
Dahlia
South West
Annie
Dodie
South East
Annie
Dahlia
East
Ash
Bill
Cecilia
Dave
West
Art
Bruce
Colin
Danny
Output

Cool, the table is formed but notice, how ChatGPT has added background colors too!
But the data isn’t accurate.
Gemini
Code by Gemini
Region
Manager
Coordinator
Local Rep
Secretary
North
Adam
Beth
Callum
Diane
South West
Annie
Dodie
Dodie
South East Upper
Dylan
South East Lower
Dahlia
South East
Annie
Dahlia
Dahlia
East
Ash
Bill
Cecilia
Dave
West
Art
Bruce
Colin
Danny
Output

Here though, no background color
Also, inaccurate data.
DifferenceThe instant difference between the two outputs is that ChatGPT added borders in the table whereas Gemini didn’t.Additionally, ChatGPT tried to retain the styles from the original image as much as possible, that’s why we see the red and green colors for some of the rows in the HTML table.Functionality wise, both the outputs are inaccurate however, Gemini might be more accurate than ChatGPT here as if we do some manual edits, Gemini’s output should take less time to make it 100% accurate.ConclusionOkay, so there can be more criteria on the basis of which, both these tools can be compared such as debugging, etc. But to me, both are fine as combined with a bit of my own effort plus their output, the job gets done.
But…
Which one is better according to you? Any inputs?
Read more: Use This AI Tool to Generate Alphabets Out of Anything
The post ChatGPT vs Gemini — From a Frontend Developer’s POV appeared first on Suman Sourabh | Tech Blog.
September 28, 2024
Use This AI Tool to Generate Alphabets Out of Anything
I just found a cool and fun Alphabet Generator AI tool that can generate images of all the 26 letters of the English alphabet based on a prompt.
Introducing GenType.
Table of ContentsWhat is GenType?Steps to use GenType1. Sign in2. Write a prompt Prompt Testing1. Prompt 1Scope of Improvement2. Prompt 23. Prompt 3ConclusionWhat is GenType?According to Google, GenType is an AI experiment that helps anyone create a custom alphabet out of anything, using Imagen 2. With a single prompt, generate all 26 letters of the alphabet.

Click on “Sign in with Google button” and sign in with your preferred Google account.

On the left, just type a prompt which will tell GenType from which material or thing the alphabets will be made of.

For example, I wrote this prompt: “just flowers”
This was the result:
GenType just took the prompt and generated all the 26 letters in the English alphabet with flowers.

Basically, it arranged all the flowers so that they form a specific letter.
Cool, isn’t it?
I can even open a specific letter image and regenerate it if I don’t like it, copy it and download it on my device.

That’s fine and all but let’s test the responses of GenType with some more prompts.
Prompt Testing1. Prompt 1“cats”
Here’s the response:

My expectation was that GenType will generate the images of alphabets using different cat positions and forms.
But in reality, it used multiple cats to form a specific letter.
By the way you can also write anything using the newly generated alphabets.

If you look at the letter “S” image carefully, can you determine what’s happening there?

Because I really don’t know what’s happening in the above image, but this is significantly different than the other letter images.
Maybe GenType could have provided an image using the formula applied on the other images.
Let me know what you think.
2. Prompt 2“a programming code font with dark background”
Response:

Honestly, I didn’t even know what to expect from the above prompt. But at first thought, I expected that the letter images would have single letters with a typeface used in the context of writing code like “Consolas” on VS Code.
But in the response, GenType clubbed different words together and arranged it accordingly to form a specific letter.

3. Prompt 3Do you think that GenType could have done something different here?
“a convoy of cars on a mountain road on a sunny weather”
Response:

My expectation was that the images will have realistic roads that would have curves based on the letters of the English alphabet just like the letter Z below:

Instead majority of the images had the cut out portion of the roads and vehicles and simply were pasted over a mountain’s image.

Although there is a lot of improvement needed in some cases, GenType does a “not bad” job of generating alphabet images out of nothing!
Try GenType, the new Alphabet Generator AI tool by Google Labs.
Which prompts did you write?
Read more: 5 Ways I Use ChatGPT as a Frontend Web Developer
The post Use This AI Tool to Generate Alphabets Out of Anything appeared first on Suman Sourabh | Tech Blog.
September 7, 2024
This is How I Convert PDF to Markdown
I have used multiple online tools to convert PDF documents to Markdown format, but none of them came close to Marker.
Along with basic Markdown conversion, it formats tables, converts most equations to latex, extracts and stores images.
Here’s how I use Marker to extract PDF content and convert them into valid Markdown.
Environment UsedWindows 11
PrerequisitesAs per Marker’s GitHub repository, it requires the installation of:
PythonPyTorch
Go to Python Downloads page and download the latest version of Python.

Install the setup by following the instructions.
2. Install PyTorchNote: For PyTorch to be installed correctly, you must have Python 3.8 or higher installed on your system.
To install PyTorch, go to its official website and you will something like the image below:

You can tweak those options to see which one would fit best for your system. Once you have your command, open PowerShell or Command Prompt and paste your command there.
Here’s the command that I used to install PyTorch:
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118PyTorch will begin installing on your system…

It will take some time to download and install as the main file amounts to a size of 2.7 GB.
After a few minutes, PyTorch will be installed.

Now, the prerequisites are over. Next, you can go ahead and move to the actual Marker stuff.
Clone MarkerYou can clone Marker project on your local system with the following command:
git clone https://github.com/VikParuchuri/marke...After cloning, the Marker GitHub repo would look something like this:

We have cloned the repository but still we cannot convert the PDFs into Markdown format as we haven’t installed Marker.
Steps to Install Marker1. Create new environmentOutside of the newly cloned Marker GitHub repository, create a new environment for converting PDF to Markdown files.
python -m venv myenvThis will create a myenv folder consisting of multiple files.

This will activate the the newly created environment.

This command will actually install marker-pdf with the pip package manager.
pip install marker-pdfNow we are ready to convert PDF documents to Markdown files!
4. Convert PDF format to Markdown
To convert a PDF to Markdown, we need two things:
Input path of the PDF Output pathBecause the command for the conversion is something like this:
marker_single "input_path" "output_path" - batch_multiplier 2 - max_pages 12Hence, inside the cloned marker GitHub project folder, I will create two folders:
pdfs: My input folderoutput: My output folderAnd I will use a sample PDF for the Markdown conversion and paste it inside the pdfs folder.

Now, to convert the PDF “Get_Started_With_Smallpdf.pdf”, I will use the following command:
marker_single "D:/projects/marker-pdf/marker/pdfs/Get_Started_With_Smallpdf.pdf" "D:/projects/marker-pdf/marker/output" - batch_multiplier 2 - max_pages 12Here’s what the other two arguments mean according to Marker GitHub repo :
--batch_multiplier is how much to multiply default batch sizes by if you have extra VRAM. Higher numbers will take more VRAM, but process faster. Set to 2 by default. The default batch sizes will take ~3GB of VRAM.
--max_pages is the maximum number of pages to process. Omit this to convert the entire document.
Once the command is executed, Marker will initiate the conversion and save the Markdown to the output folder.

The cool thing about Marker is that it extracts all the images associated with the PDF and stores it along with the main .md (Markdown) file.


Awesome! We have converted the PDF into Markdown. But wait!! How’s the output in Markdown look?
PDF InputHere’s the PDF that we supplied Marker as the input file

Ready to take document management to the next level?

## Digital Documents—All In One Place
With the new Smallpdf experience, you can
 freely upload, organize, and share digital documents. When you enable the 'Storage' option, we'll also store all processed files here.
## Enhance Documents In One Click
When you right-click on a file, we'll present
 you with an array of options to convert, compress, or modify it.
## Access Files Anytime, Anywhere
You can access files stored on Smallpdf from

your computer, phone, or tablet. We'll also sync files from the Smallpdf Mobile App to our online portal
## Collaborate With Others
Forget mundane administrative tasks. With Smallpdf, you can request e-signatures, send large files, or even enable the Smallpdf G Suite App for your entire organization.
Pretty good, right?
ConclusionIn this tutorial, we used Marker to extract the content of a PDF and convert it into Markdown format.
Of course, the PDF had only one page, but Marker is capable of a lot many pages and it does a fine job with that!
You can try and play with it yourself!
The post This is How I Convert PDF to Markdown appeared first on Suman Sourabh | Tech Blog.