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-sitewhere 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, and api/ — 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 =&gt; (
          <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 =&gt; ({
    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 }) =&gt; {
  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 = () =&gt; {
  const [posts, setPosts] = useState([]);

  useEffect(() =&gt; {
    const fetchData = async () =&gt; {
      const res = await fetch('/api/posts');
      const data = await res.json();
      setPosts(data);
    };

    fetchData();
  }, []);

  return (
    <div>
      <h2>Динамические посты</h2>
      <ul>
        {posts.map(post =&gt; (
          <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 = () =&gt; {
  const [posts, setPosts] = useState([]);

  useEffect(() =&gt; {
    const fetchPosts = async () =&gt; {
      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 =&gt; (
          <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 = () =&gt; {
  const [posts, setPosts] = useState([]);

  useEffect(() =&gt; {
    const fetchPosts = async () =&gt; {
      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 =&gt; (
          <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.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *