How to Combine Next.js and Go: The Basics
In this article, we'll look at how integrating these two technologies can help you build dynamic websites.
Creating a Static Site with Next.js
First of all, let's create a new project using the command create-next-app
.
Open the terminal and execute the following command:
npx create-next-app my-static-site
cd my-static-site
This command will create a new directory my-static-site
where the project will be located. Next, go to this directory.
After initializing the project, you can see the following structure:
my-static-site/
├── node_modules/
├── public/
├── styles/
│ └── Home.module.css
├── pages/
│ ├── api/
│ ├── _app.js
│ ├── index.js
└── package.json
public/: This is where you can store static files such as images or fonts.
styles/: folder for CSS styles of your application.
pages/: This is the heart of the application, where each JavaScript file corresponds to a route. For example,
index.js
— this is the root route, andapi/
— for API routes.
Now we implement static generation using methods getStaticProps
And getStaticPaths
.
Static generation methods
getStaticProps
: Allows data extraction during build time, meaning that pages will be pre-generated with data. This somewhat improves SEO and loading speed.
Example of use getStaticProps
:
// pages/index.js
export async function getStaticProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return {
props: {
data,
},
};
}
const Home = ({ data }) => {
return (
<div>
<h1>Статический сайт на Next.js</h1>
<ul>
{data.map(item => (
<li>{item.title}</li>
))}
</ul>
</div>
);
};
export default Home;
Here we make a request to the API and pass data to the component as props.
getStaticPaths
: used to dynamically generate pages based on route parameters.
Example of use getStaticPaths
:
// pages/posts/[id].js
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
const paths = posts.map(post => ({
params: { id: post.id.toString() },
}));
return { paths, fallback: false };
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.id}`);
const post = await res.json();
return {
props: {
post,
},
};
}
const Post = ({ post }) => {
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
};
export default Post;
In this example, we generate pages for each post based on data from the API. getStaticPaths
creates an array of paths, and getStaticProps
gets data for a specific post.
The next step is to use dynamic routes to load data. To do this, we will create an API route that will return data, and then integrate it into the site.
Let's create a file pages/api/posts.js
:
// pages/api/posts.js
export default async function handler(req, res) {
const response = await fetch('https://api.example.com/posts');
const posts = await response.json();
res.status(200).json(posts);
}
Now there is an API that returns a list of posts. You can use it in your application.
To load data asynchronously, you can use fetch
V useEffect
in the component:
import { useEffect, useState } from 'react';
const DynamicContent = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
const fetchData = async () => {
const res = await fetch('/api/posts');
const data = await res.json();
setPosts(data);
};
fetchData();
}, []);
return (
<div>
<h2>Динамические посты</h2>
<ul>
{posts.map(post => (
<li>{post.title}</li>
))}
</ul>
</div>
);
};
export default DynamicContent;
It is important to handle errors when working with API. You can use blocks try/catch
in functions:
try {
const res = await fetch('/api/posts');
if (!res.ok) throw new Error('Ошибка сети');
const data = await res.json();
setPosts(data);
} catch (error) {
console.error('Ошибка при загрузке данных:', error);
}
Go Integration: Setting Up the Server Side
Let's add Gin depending on:
go get -u github.com/gin-gonic/gin
Now we can start creating the API. Create a file main.go
and add the following code:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type Post struct {
ID string `json:"id"`
Title string `json:"title"`
Body string `json:"body"`
}
var posts = []Post{
{ID: "1", Title: "Post 1", Body: "This is the body of post 1."},
{ID: "2", Title: "Post 2", Body: "This is the body of post 2."},
}
func main() {
r := gin.Default()
r.GET("/api/posts", getPosts)
r.GET("/api/posts/:id", getPostByID)
r.Run(":8080") // Запускаем сервер на порту 8080
}
func getPosts(c *gin.Context) {
c.JSON(http.StatusOK, posts)
}
func getPostByID(c *gin.Context) {
id := c.Param("id")
for _, post := range posts {
if post.ID == id {
c.JSON(http.StatusOK, post)
return
}
}
c.JSON(http.StatusNotFound, gin.H{"message": "post not found"})
}
Created two routes: one to get all posts, and one to get a post by ID. If the post is not found, return status 404 and an error message.
Now that the API is ready, let's look at how to access it from a Next.js application.
Let's say we need to get a list of posts. Open the file pages/index.js
Next.js application and add the following code:
import { useEffect, useState } from 'react';
const Home = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
const fetchPosts = async () => {
try {
const res = await fetch('http://localhost:8080/api/posts');
if (!res.ok) throw new Error('Ошибка при получении данных');
const data = await res.json();
setPosts(data);
} catch (error) {
console.error('Ошибка:', error);
}
};
fetchPosts();
}, []);
return (
<div>
<h1>Посты</h1>
<ul>
{posts.map(post => (
<li>{post.title}</li>
))}
</ul>
</div>
);
};
export default Home;
Don't forget to pay attention to error handling when executing a request. If the server returns an error, we output a message to the console. This is important for debugging and informing the user about problems.
Instead of built-in fetch
You can use the Axios library to make HTTP requests. Let's install Axios:
npm install axios
Then we change the code to the following:
import axios from 'axios';
import { useEffect, useState } from 'react';
const Home = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
const fetchPosts = async () => {
try {
const { data } = await axios.get('http://localhost:8080/api/posts');
setPosts(data);
} catch (error) {
console.error('Ошибка:', error);
}
};
fetchPosts();
}, []);
return (
<div>
<h1>Посты</h1>
<ul>
{posts.map(post => (
<li>{post.title}</li>
))}
</ul>
</div>
);
};
export default Home;
API security is very important. One simple way is to use JWT tokens to authenticate users. You can use the package github.com/dgrijalva/jwt-go
to work with JWT.
To install:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type Post struct {
ID string `json:"id"`
Title string `json:"title"`
Body string `json:"body"`
}
var posts = []Post{
{ID: "1", Title: "Post 1", Body: "This is the body of post 1."},
{ID: "2", Title: "Post 2", Body: "This is the body of post 2."},
}
func main() {
r := gin.Default()
r.GET("/api/posts", getPosts)
r.GET("/api/posts/:id", getPostByID)
r.Run(":8080") // Запускаем сервер на порту 8080
}
func getPosts(c *gin.Context) {
c.JSON(http.StatusOK, posts)
}
func getPostByID(c *gin.Context) {
id := c.Param("id")
for _, post := range posts {
if post.ID == id {
c.JSON(http.StatusOK, post)
return
}
}
c.JSON(http.StatusNotFound, gin.H{"message": "post not found"})
}
To enable interaction between a Next.js application and a Go API, you need to set up CORS. This can be done using the built-in middleware in Gin.
Add the following line to main.go
:
r.Use(cors.Default())
To do this, you will need to install the CORS package:
go get -u github.com/gin-contrib/cors
The API will now be able to handle requests from a Next.js application.
Don't be afraid to experiment with new features and expand the project.
You can master the profession of a Fullstack JavaScript developer in the “Fullstack developer” specialization.