How to combine Next.js and Go: basics

How to combine Next.js and Go: basics

Hello, Habre!

In modern web development, the developer mainly faces the task of creating applications that not only load quickly, but also provide a smooth user experience. Combination Next.js and Go offers a powerful solution for this task.

Next.js, with its static generation and server-side rendering capabilities, allows you to create high-performance interfaces that are easily optimized for search engines. Go, in turn, provides a robust backend capable of handling multiple requests simultaneously.

In this article, we will look at how the integration of these two technologies can help you build dynamic sites.

Creating a static site on Next.js

First of all, let’s create a new project using the command create-next-app.

Open the terminal and run the following command:

npx create-next-app my-static-site
cd my-static-site

This command will create a new directory my-static-sitewhere the project will be located. Next, we 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/: You can store static files such as images or fonts.

  • styles/: folder for your application’s CSS styles.

  • pages/: This is the heart of the application, where each JavaScript file corresponds to a route. For example, index.js is the root route, and api/ – for API routes.

Now we implement static generation using methods getStaticProps and getStaticPaths.

Methods of static generation

getStaticProps: allows data to be extracted at build time, which means pages will be pre-generated with data. This improves SEO and loading speed to some extent.

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 (
    
    {data.map(item => (
  • {item.title}
  • ))}
); }; export default Home;

Here we make a request to the API and pass the component data 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 (
    
  );
};

export default Post;

In this example, we generate pages for each post based on API data. getStaticPaths creates an array of paths, a getStaticProps receives 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);
}

There is now an API that returns a list of posts. You can use it in the program.

To load data asynchronously, you can use fetch in 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 (
    

Динамические посты

    {posts.map(post => (
  • {post.title}
  • ))}
); }; export default DynamicContent;

It is important to handle errors when working with APIs. Blocks can be used 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);
}

Integration with Go: configuring the backend

Add Gin depending on:

go get -u github.com/gin-gonic/gin

Now you can start building your API. We 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"})
}

We created two routes: one for receiving posts, and the other for receiving a post by ID. If the post is not found, we return a 404 status and an error message.

Now that the API is ready, let’s see how to access it from Next.js.

Suppose you need to receive a list of posts. Open the file pages/index.js Next.js program 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 (
    
    {posts.map(post => (
  • {post.title}
  • ))}
); }; export default Home;

Let’s not 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 of 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 (
    
    {posts.map(post => (
  • {post.title}
  • ))}
); }; export default Home;

API security is very important. One simple way is to use JWT tokens to authenticate users. You can use a 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 ensure interaction between Next.js application and API on Go, you need to configure 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 need to install the CORS package:

go get -u github.com/gin-contrib/cors

The API will now be able to handle requests from the 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 on the “Fullstack developer” specialization.

Related posts