Привет, Хабр!

В современной веб-разработке перед разработчиком в основном стоит задача создать приложения, которые не только быстро загружаются, но и дают плавный пользовательский опыт. Сочетание Next.js и Go предлагает мощное решение для этой задачи.

Next.js, с его возможностями статической генерации и серверного рендеринга, позволяет создавать высокопроизводительные интерфейсы, которые легко оптимизируются для поисковых систем. Go, в свою очередь, дает надежный бэкенд, способный обрабатывать множество запросов одновременно.

В этой статье рассмотрим, как интеграция этих двух технологий может помочь вам построить динамические сайты.

Создание статического сайта на Next.js

Первым делом создадим новый проект с помощью команды create-next-app.

Открываем терминал и выполняем следующую команду:

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

Эта команда создаст новую директорию my-static-site, где будет располагаться проект. Далее переходим в эту директорию.

После инициализации проекта можно увидеть следующую структуру:

my-static-site/
├── node_modules/
├── public/
├── styles/
│   └── Home.module.css
├── pages/
│   ├── api/
│   ├── _app.js
│   ├── index.js
└── package.json
  • public/: здесь можно хранить статические файлы, такие как изображения или шрифты.

  • styles/: папка для CSS-стилей вашего приложения.

  • pages/: это сердце приложения, где каждая JavaScript-файл соответствует маршруту. Например, index.js — это корневой маршрут, а api/ — для API-роутов.

Теперь реализуем статическую генерацию с помощью методов getStaticProps и getStaticPaths.

Методы статической генерации

getStaticProps: позволяет извлекать данные во время сборки, что означает, что страницы будут предгенерированы с данными. Это в чем-то улучшает SEO и скорость загрузки.

Пример использования 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;

Здесь делаем запрос к API и передаем данные в компонент в качестве пропсов.

getStaticPaths: используется для динамической генерации страниц на основе параметров маршрута.

Пример использования 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;

В этом примере генерируем страницы для каждого поста на основе данных из API. getStaticPaths создает массив путей, а getStaticProps получает данные для конкретного поста.

Следующий шаг — использование динамических маршрутов для загрузки данных. Для этого создадим API-роут, который будет возвращать данные, а затем интегрируем его в сайт.

Создадим файл 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);
}

Теперь есть API, который возвращает список постов. Можно использовать его в приложении.

Чтобы загружать данные асинхронно, можно использовать fetch в useEffect в компоненте:

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;

Важно обрабатывать ошибки при работе с API. Можно использовать блоки try/catch в функциях:

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: настройка серверной части

Добавим Gin в зависимости:

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

Теперь можно приступить к созданию API. Создаем файл main.go и добавляем следующий код:

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"})
}

Создали два маршрута: один для получения всех постов, а другой для получения поста по ID. Если пост не найден, возвращаем статус 404 и сообщение об ошибке.

Теперь, когда API готов, посмотрим, как обращаться к нему из приложения Next.js.

Предположим, нужно получить список постов. Открываем файл pages/index.js Next.js приложения и добавляем следующий код:

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;

Не забываем обратить внимание на обработку ошибок при выполнении запроса. Если сервер возвращает ошибку, мы выводим сообщение в консоль. Это важно для отладки и информирования пользователя о проблемах.

Вместо встроенного fetch можно использовать библиотеку Axios для выполнения HTTP-запросов. Установим Axios:

npm install axios

Затем изменяем код на следующий:

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 — это очень важно. Один из простых способов — использовать токены JWT для аутентификации пользователей. Можно использовать пакет github.com/dgrijalva/jwt-go для работы с JWT.

Для установки:

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"})
}

Для обеспечения взаимодействия между Next.js приложением и API на Go, нужно настроить CORS. Это можно сделать с помощью встроенного middleware в Gin.

Добавляем следующую строку в main.go:

r.Use(cors.Default())

Для этого потребуется установить пакет CORS:

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

Теперь API сможет обрабатывать запросы из Next.js приложения.


Не бойтесь экспериментировать с новыми функциями и расширять проект.

Освоить профессию Fullstack‑разработчика на JavaScript можно на специализации «Fullstack developer».

Комментарии (5)


  1. qrKot
    25.09.2024 04:49
    +2

    Я ведь верно понял, что серверная часть Next.js тупо рендерит по шаблону? Ну, т.е. зачем-то делает ровно то, для чего предназначен пакет html/template в Go?


    1. tumbler
      25.09.2024 04:49
      +1

      Не, там сложнее. Страница целиком выполняется на сервере - с запросами к API и всеми остальными скриптами, но при этом сохраняется как статика. Огромный плюс - не надо реализовывать шаблоны и логику дважды (поэтому кстати вообще непонятно, причём тут Go).


      1. qrKot
        25.09.2024 04:49

        ну, плюс такой себе, имхо. За серверные мощности платим мы, за клиентские (не замечая) - клиент.

        По сути же (наискосок статью прочел): создается стойкое впечатление, что либо Go, либо Next.js тут нафиг не нужно. Что-то явно лишнее, при этом потенциал Go даже наполовину не используется (по Next.js не скажу, профиль больше в Go).


  1. savostin
    25.09.2024 04:49
    +1

    Ничего не понял. Вы из фронта запрашиваете у сервера на JavaScript страницу, которую он рендерит, запрашивая API на Go?

    Хмммм.


  1. kaufmanendy
    25.09.2024 04:49

    хаб не тот, а вообще тут описание преимуществ SSR от NextJS, но термин SSR не упомянут