Hi, I'm @ryusei__46.
In this article, we have created a CMS-like template in Next.js that allows users to post articles just like WordPress. If you are interested in using Next.js to manage a blog or home page from now on, please try using it as a template.
The blog template we created this time is the same as the source of this site, so if you want to change the design drastically, it would be a hassle, so it might be better to code from scratch with Next.js.
From the beginning, it is quite laborious to develop a blog or homepage system in Next.js. I was learning as I went along, so it took me about two and a half months to get it into shape.
If you are building a blog or homepage as a complete SSG (Static Site Generator) by building each page individually, the development will be finished rather smoothly, but if you want to add management functions, the man-hours will jump up.
However, by adding article management functions,
- Easy article submission
- No need to add and build markdown and HTML files for each page, which saves a lot of man-hours.
- Build size can be reduced
- Build time is reduced
- By managing articles in a database, logic such as displaying related articles, comment functions, and category associations can be implemented.
It is possible to realize functions essential to many CMSs, such as
So, I will now open the template, which I have created.
Specifications of the blog template produced (tentative version)
This blog template is still under development, and we will be adding more features as we go along. Also, there are still many bugs, so you will need to fix them yourself each time you use it.
- Article submission and editing (including drafts and permalink settings)
- Media Library
- Category Management
Other logic, page layout, etc. are the same as on this site and can be used as is, but will be customized on an individual basis.
Structure and file army of the blog template produced
This blog template is built with App Router using Next.js 14.0.2.
First, let us describe the packages and development environment used.
development environment
OS is Windows 11 Pro, developing on WSL2 (Ubuntu 22.04), using Node.js 20.5.1, pnpm 8.10.2 (faster than package manager/npm).
Packages used
dependencies:
@next-auth/prisma-adapter 1.0.7 file-type 18.5.0 next-auth 4.24.3 @prisma/client 5.4.2 jschardet 3.0.0 nodemailer 6.9.6
bcrypt 5.1.1 jsdom 22.1.0 sanitize-html 2.11.0 ckeditor5-custom-build file:lib/ckeditor5-custom negotiator 0.6.3 sharp 0.32.6
cookies-next 4.0.0 next 14.0.2
devDependencies:
@formatjs/intl-localematcher 0.4.2 @types/negotiator 0.6.2
prisma 5.4.2 @fortawesome/fontawesome-svg-core 6.4.2
@types/node 20.8.7 react 18.2.0
@fortawesome/free-brands-svg-icons 6.4.2 @types/nodemailer 6.4.13
react-dom 18.2.0 @fortawesome/free-regular-svg-icons 6.4.2
@types/react 18.2.31 react-hook-form 7.47.0
@fortawesome/free-solid-svg-icons 6.4.2 @types/react-dom 18.2.14
react-select 5.7.7 @fortawesome/react-fontawesome 0.2.0
@types/sanitize-html 2.9.3 sass 1.69.4
@types/bcrypt 5.0.1 animate.css 4.1.1
tocbot 4.21.2 @types/jsdom 21.1.4
highlight.js 11.8.0 typescript 5.2.2
The ckeditor5-custom-build
package is a custom build of CkEditor 5. It includes the officially provided plugins and our own implementation of the plugins.
directory structure
├── app
│ └── [lang] // Parameters for language judgment
│ ├── admin // Management Panel Pages
│ │ ├── category
│ │ ├── media
│ │ ├── post
│ │ └── post-list
│ ├── api
│ │ ├── auth // Authentication process used to process login to the management panel
│ │ ├── blogcard // Obtaining blog card information
│ │ ├── category
│ │ │ ├── create
│ │ │ ├── get
│ │ │ │ ├── related-post
│ │ │ │ └── route.ts
│ │ │ ├── slug-exist // Get category from slug
│ │ │ └── update
│ │ ├── contact // Email sending process used for inquiries
│ │ ├── discussion // Article Comment Management
│ │ │ ├── create
│ │ │ └── get
│ │ ├── get-media
│ │ ├── get-media-first
│ │ ├── media-stream // Stream images from stored media
│ │ │ ├── icon
│ │ ├── post // Article Information Management
│ │ │ ├── create
│ │ │ ├── get
│ │ │ ├── get-many
│ │ │ └── update
│ │ ├── register // User Registration
│ │ ├── rm-media // Media Deletion
│ │ ├── upload // Upload Media
│ │ └── userexist // User existence check
│ ├── article // 記事ページ
│ │ ├── [permalink] // View article by permalink
│ ├── auth // User Authentication Page
│ │ ├── login
│ ├── category // Display of new articles by category
│ │ ├── [slug] // Display with slugs
│ └── privacy-policy // Privacy Policy Page
├── components
│ ├── small-parts
│ └── use-client
├── external // Scripts that need to be processed in the script tag
├── lib
│ └── ckeditor5-custom
├── locales // Language dictionary files and scripts for multilingualization
├── prisma // Database Schema Management
│ └── migrations
└── types // Type definition file army for TypeScript
├── define-type.d.ts
└── override-react.d.ts
The above directory structure should give you a general idea of the overall picture.
How to use the blog template you created
Below is a brief description of how to use and customize the system.
Download templates and build environment
You can download the source from my GitHub repository below or clone it to your project directory with git clone.
When ready, run the pnpm install
command in the root directory of the project to install the necessary dependent packages.
Setting environment variables in the .env file
There is a ".env.sample" file in the project root, so first delete the ".sample" extension and rename it to ".env" file.
When you open the ".env" file you created, you will see the following contents, so please change all the values to those appropriate for your individual environment.
# App settings
APP_ROOT_PATH=/var/www/next-creator // Absolute path to the project root
API_ACCESS_ADDRESS="http://192.168.1.45:8648" // For fetching by the server component
NEXT_PUBLIC_SITE_TITLE="site title"
APP_URL=http://ryuseiweb.localhost
NEXT_PUBLIC_APP_URL=http://ryuseiweb.localhost
API_ACCESS_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# Google Analytics
NEXT_PUBLIC_GA_MEASUREMENT_ID = "G-xxxxxxxxxx"
# next-auth
NEXTAUTH_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
NEXTAUTH_URL=http://ryuseiweb.localhost
# smtp server setting
SMTP_HOST="smtp-mail.outlook.com"
SMTP_USER="xxxxxxx@xxxxxxxxxx"
SMTP_PASSWORD="xxxxxxxxxxx"
USE_ADDRESS="xxxxxxxxxxx@xxxxxxxxxxx"
# Prisma
DATABASE_URL="mysql://[username]:[password]@[hostname]:[port]/[databasename]"
Regarding API_ACCESS_ADDRESS
, for some reason an error occurred when using the domain name as the fetch in the server component when doing SSR, so I used the IP address directly. We have not verified this issue since then, so the bug may have been resolved with the newer version of Next.js.
We use Microsoft Outlook as our outgoing mail server, but if you would like to use another SMTP server, please change it accordingly. However, since the specifications of SMTP servers differ depending on the service, please change the settings of nodemailer
by yourself. You can find the file in /api/[lang]/api/contact
.
Configuration of public.config.json file
In the project root, there is a "public.config.json" file. This file contains the multilingualization settings for the site at this time.
{
"locale": {
"default": "ja",
"accept-lang": [ "ja", "en" ],
"labels": {
"ja": { "ja": "日本語", "en": "英語" },
"en": { "ja": "Japanese", "en": "English" }
}
}
}
The "default
" key sets the language that will be applied to the site by default. The "accept-lang
" key lists an array of language codes that can be used site-wide. The "labels
" key sets the label settings to be displayed in the language selection dropdown for each language.
*At least one language code must be set.
Also, set an array of available language codes for "Type: AcceptLocales
" used in TypeScript. You can find it in the third line of /types/define-type.d.ts
.
Initialize database with Prisma
First, generate a TypeScript type definition from the prisma model with the following command
pnpx prisma generate
Then, initialize the database with the following command
pnpx prisma migrate dev
You have now created the necessary tables in the database.
Server startup
Start the server with the command pnpm run dev
Access http://localhost:8648
and you will see the top page of the blog.
Next, go to http://localhost:8648/auth/login
and you will see a user registration form. If the registration is successful, you will be logged in automatically.
Once logged in, an administration bar will appear at the top of the page, from which you can edit articles, manage categories, and upload media.
*At this point, only the minimum functions are available, and we plan to update them as needed.
Explanation of the article list screen
This page lists the posts you have submitted. For each post, you can edit and delete it.
Explanation of article submission screen
On this page, you will post and edit articles. The editor is CkEditor 5, which is similar to the editor that comes with WordPress. It is a bit of a hassle, but if you want to be able to handle your own blocks and woozies, you will need to write your own plugins. I have created plug-ins for captcha blocks, blog card blocks, media selection functions, etc., and incorporated them into CkEditor. I have also added and built all the free official plug-ins provided by the developer of CkEditor that I think are necessary.
The tab with the language name at the top of the editor allows you to edit the article content separately for each country language. This tab is displayed based on the language settings configured in "public.config.json".
In the "Thumbnail Settings" section on the right sidebar, the thumbnail image for the article is set. If no thumbnail image is set, a "No Image" image will be inserted.
The permalink
allows you to specify an alphanumeric, easy-to-understand string as the URL for displaying the article, similar to a WordPress permalink. If you do not specify a permalink, the id will be inserted in the URL of the article. If you specify a permalink: https://example.com/article/my_post_title
, if not: https://example.com/article?id=xx
.
In the "Category Settings" section, you can click on an item to associate it with the category article you have set.
Finally, click "Publish" to post the article, or "Save as Draft" to save it as a draft.
Media Library Description
The media library currently allows only image uploads. Uploading of audio and video files will be supported as needed.
Rather than uploading videos, it is better to post them to YouTube or other video posting sites and embed the player so as not to overwhelm server space.
Click on each media for a larger view.
Explanation of category setting screen
This page allows you to add, edit, and delete categories. Category configuration items include category name (you can set the label to be displayed in each language), slug, rank (originally provided to set the order of display, but this value is currently not used), selection of parent category, and icon image (icon to be displayed to the left of the category label. If not set, the default Font Awesome icon will be displayed.) can be set.
catchphrase
The front page section is the same as this site, so we omit the explanation. If you wish to customize the layout individually, please do so yourself.
Also, one thing to keep in mind, if you want to load external scripts or stylesheets, please add a Content-Security-Policy
setting. It is located in /middleware.ts
.
We currently have the following settings.
// ・・・・・・・
const cspHeader = `
default-src 'self';
script-src 'self' https://www.googletagmanager.com https://www.google-analytics.com 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
style-src-elem 'self' https://fonts.googleapis.com 'unsafe-inline';
img-src 'self' https://* blob: data:;
font-src 'self' https://fonts.gstatic.com;
object-src 'none';
base-uri 'self';
form-action 'self' *;
frame-ancestors 'none';
block-all-mixed-content;
upgrade-insecure-requests;
`;
// :::::::
Conclusion
We were able to break away from WordPress this time by producing a CMS-like system with Next.js. I am currently very satisfied with this system. I still need to add features and fix bugs, but I will enjoy learning as I go.
I have not added any comments to the template I developed this time, so there may be places where it is difficult to understand, but please understand that I did not have enough time to do so. If you have any questions or problems, please comment on this article or contact me directly through "Contact Us". You can also direct message X (@ryusei_46).