Гид по технологиям

Клон Hacker News на React — пошаговое руководство

7 min read Frontend Обновлено 07 Jan 2026
Клон Hacker News на React — создать SPA
Клон Hacker News на React — создать SPA

Создайте свой клиент Hacker News на React — проект объясняет, как настроить Vite, подключить маршрутизацию, написать кастомный хук useFetch для двух API, отрисовать список постов, страницу поста и вложенные комментарии. В статье также собраны улучшения, чек-листы и критерии приёмки для полноценной SPA.

Логотип React поверх изображения человека, использующего ноутбук и смартфон

Почему стоит клонировать Hacker News

  • Практическая задача для закрепления React-концепций: маршрутизация, кастомные хуки, работа с REST/Algolia API, рекурсивные компоненты.
  • Проект компактный, но покрывает типичные паттерны SPA, которые пригодятся в реальных приложениях.
  • Легко расширяем: поиск, кэширование, серверный рендеринг, аутентификация.

Важно: этот материал объясняет фронтендную часть клиента — данные берутся из публичных API.

Установка проекта и запуск сервера разработки

Код проекта доступен в репозитории на GitHub под MIT-лицензией. Для стилизации скопируйте содержимое файла index.css из репозитория в ваш локальный index.css. Есть демо-версия проекта, если хотите посмотреть результат вживую.

Нужные пакеты:

  • react-router-dom — маршрутизация для SPA.
  • html-react-parser — парсинг HTML, возвращаемого API (комментарии, текст).
  • moment — работа с датами (человеко-понятные относительные времена).

Откройте терминал и выполните:

yarn create vite  

Можно использовать npm вместо yarn при желании. Команда создаст каркас проекта Vite. Дайте проекту имя, выберите React и вариант JavaScript.

Зайдите в папку проекта и установите зависимости:

yarn add html-react-parser  
yarn add react-router-dom  
yarn add moment  
yarn dev  

После установки и запуска dev-сервера откройте проект в редакторе и создайте в папке src три каталога: components, hooks, pages.

В components: добавьте Comments.jsx и Navbar.jsx. В hooks: useFetch.jsx. В pages: ListPage.jsx и PostPage.jsx.

Удалите App.css и замените содержимое main.jsx на следующее (сохраните синтаксис):

import React from'react'  
import { BrowserRouter } from'react-router-dom'  
import ReactDOM from'react-dom/client'  
import App from'./App.jsx'  
import'./index.css'  
  
ReactDOM.createRoot(document.getElementById('root')).render(  
  
  
  
  
,  
)  

В App.jsx оставьте минимальный функциональный компонент:

functionApp() {  
return (  
<>  
  
)  
}  
  
exportdefault App  

Импортируйте модули маршрутизации и страницы в App.jsx:

import { Routes, Route } from'react-router-dom'  
import ListPage from'./pages/ListPage'  
import Navbar from'./components/Navbar'  
import PostPage from'./pages/PostPage'  

Внутри React-фрагмента добавьте Routes с тремя маршрутами: /, /:type, /item/:id:

  
 }>  
  
 }>  
  
}>  
  
  

Совет по удобству разработки: используйте расширение редактора для авто-форматирования и линтинга, чтобы поддерживать единый стиль кода.

Создание кастомного хука useFetch

Проект использует два API:

  • node-hnapi.herokuapp.com — возвращает списки постов по категориям (news, best, show, ask, jobs).
  • hn.algolia.com — возвращает подробности поста и дерево комментариев.

Создайте файл useFetch.jsx и экспортируйте хук по умолчанию. Импортируйте useState и useEffect.

import { useState, useEffect } from"react";  
exportdefaultfunctionuseFetch(type, id) {  
  
}  

Определите состояния data, error, loading:

const [data, setData] = useState();  
const [error, setError] = useState(false);  
const [loading, setLoading] = useState(true);  

Добавьте useEffect с зависимостями id и type:

useEffect(() => {  
}, [id, type])  

Внутри эффекта реализуйте fetchData, выбирая URL и параметр в зависимости от переданных аргументов:

asyncfunctionfetchData() {  
let response, url, parameter;  
if (type) {   
url = "https://node-hnapi.herokuapp.com/";   
parameter = type.toLowerCase();   
}  
elseif (id) {   
url = "https://hn.algolia.com/api/v1/items/";   
parameter = id.toLowerCase();   
}  
try {  
response = await fetch(`${url}${parameter}`);  
} catch (error) {  
setError(true);  
}  
  
if (response) if (response.status !== 200) {  
setError(true);  
} else {  
let data = await response.json();  
setLoading(false);  
setData(data);  
}  
}  
fetchData();  

Наконец, верните объект { loading, error, data }:

return { loading, error, data };  

Рекомендация по улучшению: добавьте AbortController для отмены незавершённых запросов при размонтировании компонента, и минимальное кеширование, чтобы снизить число сетевых запросов.

Отрисовка списка постов в зависимости от категории

Компонент ListPage должен рендериться для маршрутов / и /:type.

Импортируйте модули и хук:

import { useNavigate, useParams } from"react-router-dom";  
import useFetch from"../hooks/useFetch";  

Определите компонент, получите параметр type и вызовите useFetch:

exportdefaultfunctionListPage() {  
let { type } = useParams();  
const navigate = useNavigate();  
if (!type) type = "news";  
const { loading, error, data } = useFetch(type, null);  
}  

Возвратите JSX в зависимости от состояния:

if (error) {  
return
Something went wrong!
} if (loading) { return
Loading
} if (data) { document.title = type.toUpperCase(); return
{type}
{data.map(item =>
navigate(`/item/${item.id}`)}> {item.title}
{item.domain && open(`${item.url}`)}> ({item.domain})}
)}
}

UX-совет: добавьте индикатор пагинации или lazy-load для длинных списков и aria-атрибуты для доступности.

Компонент PostPage

Импортируйте нужные пакеты и компоненты, получите id из параметров и вызовите useFetch:

import { Link, useParams } from"react-router-dom";  
import parse from'html-react-parser';  
import moment from"moment";  
import Comments from"../components/Comments";  
import useFetch from"../hooks/useFetch";  
  
exportdefaultfunctionPostPage() {  
const { id } = useParams();  
const { loading, error, data } = useFetch(null, id);  
}  

Рендерьте разные состояния так же, как в ListPage:

if (error) {  
return
Something went wrong!
} if (loading) { return
Loading
} if (data) { document.title=data.title; return
{data.title}
{data.url && Visit Website} {data.author} {moment(data.created_at).fromNow()}
{data.text &&
{parse(data.text)}
}
Comments
}

Совет: проверяйте presence полей (data?.children), чтобы избежать ошибок, если API вернул неожидаемые данные.

Рендеринг комментариев с вложенными ответами

Импортируйте parse и moment. Компонент Comments принимает массив commentsData и рендерит Node для каждого элемента.

import parse from'html-react-parser';  
import moment from"moment";  
  
exportdefaultfunctionComments({ commentsData }) {  
return<>  
{commentsData.map(commentData => )}  
  
}  

Под компонентом Comments определите Node, который рекурсивно рендерит самого себя для детей (children):

functionNode({ commentData }) {  
return  
{  
commentData.text &&  
<>  
{commentData.author} {moment(commentData.created_at).fromNow()}
{parse(commentData.text)}
}
{(commentData.children) && commentData.children.map(child => )}
}

Заметка: parse безопасен для HTML, но будьте внимательны с пользовательским вводом в других проектах — проверяйте XSS-риски.

Навигация

Откройте Navbar.jsx, импортируйте NavLink из react-router-dom и верните nav с пятью ссылками на категории:

import { NavLink } from"react-router-dom"  
  
exportdefaultfunctionNavbar() {  
return  
}  

Поздравляем — вы собрали клиент Hacker News!

Скриншот готового клона

Как закрепить навык: идеи для улучшений

  1. Поиск и автодополнение: подключите Algolia search API для быстрого поиска по заголовкам и пользователям.
  2. Кэширование и рефетчинг: используйте React Query / SWR для более надежного кэширования и обновления данных.
  3. TypeScript: добавьте типизацию для безопасности и автодополнения.
  4. Тестирование: добавьте unit и интеграционные тесты (Jest + React Testing Library).
  5. SSR/ISR: для SEO можно попробовать Next.js и подгрузку первых страниц на сервере.
  6. Доступность: aria-метки, фокусная навигация, контраст цветов.

Когда этот подход не подходит

  • Если вам нужна сложная серверная логика (аутентификация, write-операции), одной клиентской реализации недостаточно.
  • Если проект требует строгой доступности и SEO с первого рендера — подумайте о серверном рендеринге.
  • Если ожидается большой трафик и высокая нагрузка API — полезно добавить слой прокси/кэша на сервере.

Чек-листы по ролям

Чек-лист для разработчика:

  • Запущен dev-сервер (yarn dev).
  • Установлены все зависимости (html-react-parser, react-router-dom, moment).
  • Созданы папки components/hooks/pages.
  • Хук useFetch возвращает loading/error/data.
  • Маршруты корректно работают: /, /best, /show, /ask, /jobs, /item/:id.
  • Комментарии корректно рендерятся рекурсивно.

Чек-лист для code-review:

  • Нет утечек памяти (очистка эффектов, AbortController).
  • Обработка ошибок и состояния загрузки есть для всех async операций.
  • Компоненты простые, соблюдён один уровень ответственности.
  • Локализация дат/времени (если требуется) учтена.

Критерии приёмки

  • При заходе на / появляется список новостей (news).
  • При переходе на /best, /show, /ask, /jobs список обновляется согласно типу.
  • При клике на заголовок происходит переход на /item/:id и отображается страница поста.
  • Комментарии отображаются с вложенностью и временем в формате «N часов/дней назад».
  • В случае ошибки сети пользователь видит понятное сообщение об ошибке.

Шпаргалка команд и зависимостей

  • Инициализация Vite: yarn create vite
  • Установка зависимостей: yarn add html-react-parser react-router-dom moment
  • Запуск dev-сервера: yarn dev

Мини-список импортов, которые вы будете часто использовать:

  • import parse from ‘html-react-parser’
  • import moment from ‘moment’
  • import { BrowserRouter, Routes, Route, NavLink, useParams, useNavigate } from ‘react-router-dom’

Тестовые сценарии и критерии приёма

  1. Загрузка списка новостей
    • Шаги: открыть /
    • Ожидается: показывает список постов, document.title = NEWS
  2. Переход в другую категорию
    • Шаги: открыть /best
    • Ожидается: список обновлён под best, ссылки работают
  3. Открытие поста
    • Шаги: кликнуть заголовок
    • Ожидается: открывается /item/:id, отображается заголовок, автор, время, текст и комментарии
  4. Обработка ошибки сети
    • Шаги: отключить интернет или замокать fetch с ошибкой
    • Ожидается: показывается сообщение “Something went wrong!” (лучше заменить на русскую локализацию в проде)

Ментальные модели и советы по архитектуре

  • Single Responsibility: каждый компонент отвечает за одну вещь — ListPage только за список, PostPage только за пост и вложенные комментарии.
  • Data-first: хук useFetch инкапсулирует получение данных — изменение источника данных (например, переход на GraphQL) требует правок только в хуке.
  • UI-as-state: состояния loading/error/data полностью определяют, что показывать.

Факт-бокс

  • Проект охватывает основные аспекты SPA: маршрутизация, загрузка данных, рекурсивные компоненты.
  • Алгоритм рендеринга комментариев реализован рекурсивно — эффективен для глубоких деревьев.

Превью для социальных сетей

OG title: Клон Hacker News на React — руководство OG описание: Пошаговое руководство по созданию клиента Hacker News: Vite, маршрутизация, useFetch, комментарии и идеи для улучшения.

Короткое объявление (100–200 слов)

Постройте свой собственный клиент Hacker News на React: быстрое пошаговое руководство показывает, как собрать SPA с Vite, react-router, кастомным хуком useFetch и рекурсивной отрисовкой комментариев через html-react-parser и moment. В статье есть готовая структура проекта, рабочие компоненты ListPage и PostPage, а также предложения по улучшению (кэширование, TypeScript, тестирование). Этот проект идеально подходит для изучения типовых паттернов фронтенд-разработки и подготовки портфолио.

Важно

  • Проверьте CORS и ограничения публичных API — в продакшене может понадобиться прокси.
  • Внедряйте защиту от XSS при парсинге HTML вне контролируемых источников.

Резюме

  • Постройте минимальную версию по шагам выше.
  • Расширяйте через проверенные библиотеки (React Query, TypeScript).
  • Пропишите критерии приёмки и тесты перед деплоем.

Краткие выводы

  • Проект даёт практику по маршрутизации, хукам и рекурсивным компонентам.
  • Легко масштабируется добавлением поиска, кэширования и типизации.
Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Отключить AirPlay на iPhone, Mac и Apple TV
Руководство

Отключить AirPlay на iPhone, Mac и Apple TV

Grammarly в React — интеграция SDK
Разработка

Grammarly в React — интеграция SDK

Диаграммы для идей и планирования
Продуктивность

Диаграммы для идей и планирования

Перенос сохранений Nintendo Switch: полный гид
Гайды

Перенос сохранений Nintendo Switch: полный гид

Звонки с Windows 11 через Intel Unison
Инструкции

Звонки с Windows 11 через Intel Unison

Клонирование жёсткого диска — руководство
Аппаратное обеспечение

Клонирование жёсткого диска — руководство