Skip to content

Commit

Permalink
Odpowiedź na artykuł „Why I won’t use Next.js” autorstwa Kent C. Dodds
Browse files Browse the repository at this point in the history
OG
  • Loading branch information
typeofweb committed Oct 28, 2023
1 parent d63b212 commit 65c6137
Show file tree
Hide file tree
Showing 20 changed files with 183 additions and 44 deletions.
1 change: 0 additions & 1 deletion .nvmrc

This file was deleted.

102 changes: 102 additions & 0 deletions _posts/2023/10/why-i-wont-use-next.js-kent-c-dodds.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
title: 'Odpowiedź na artykuł „Why I won’t use Next.js” autorstwa Kent C. Dodds'
permalink: 'why-i-wont-use-next.js-kent-c-dodds'
type: post
date: 2023-10-28T18:00:42.569Z
authors:
- michal-miszczyszyn
category: javascript
series:
slug: react-js
name: React.js
thumbnail:
url: /public/assets/uploads/2023/10/why-i-wont-use-next.js-kent-c-dodds.png
width: 1920
height: 1005
---

Kilka dni temu świat obiegł artykuł Kent C. Dodds o krzykliwym tytule „Why I won't use Next.js”. Kent krytykuje w nim Next.js, a jako przykład frameworka rozwiązującego wszystkie problemy podaje Remix. Cóż, to nie do końca tak… Poniżej znajdziecie moją odpowiedź na każdy z podpunktów.

---

## Independence

> OpenNext exists because Next.js is difficult to deploy anywhere but Vercel.
To zdanie mogłoby być prawdziwe, gdyby brzmiało „OpenNext istnieje ponieważ deployowanie Next.js na AWS z użyciem serverless jest trudne”. I to absolutnie prawda, jest to dość skomplikowane. Skomplikowane do tego stopnia, że **Remix również nie udostępnia gotowego template'u na AWS z serverless** wbrew temu co zdaje się sugerować Kent w swoim artykule.

OpenNext korzysta z SST – projektu, który powstał po to, aby ułatwić deployment aplikacji na AWS. Nie jest to specyficzne ani celowane tylko w Next.js, a wręcz przeciwnie: na stronie SST wymieniono Next.js, Astro, SvelteKit, SolidSite i… Remix.

Teraz, czy Vercel ma interes w tym, aby ułatwiać korzystanie z Next.js na innych hostingach mimo, że sam oferuje swoje płatne usługi? Oczywiście nie. Mimo to, w repozytorium Next.js znajdziemy przykłady tego, jak to robić [1](https://github.com/vercel/next.js/tree/canary/examples/with-docker-compose) [2](https://github.com/vercel/next.js/tree/canary/examples/with-docker-multi-env) [3](https://github.com/vercel/next.js/tree/canary/examples/with-docker), a dokumentacja opisuje nawet jak zaimplementować [własny Data Cache](https://nextjs.org/docs/app/api-reference/next-config-js/incrementalCacheHandlerPath).

## Next.js is eating React

> Ever since then, the React team has felt much less collaborative.
Brakuje tu jakichś konkretnych argumentów albo przykładów, a trudno się zgadzać lub nie zgadzać z czyimiś odczuciami. Z mojej perspektywy nie zmieniło się nic oprócz _velocity_ – rozwój i wdrażanie nowych rzeczy do Reacta mocno przyśpieszyło przez ostatni rok. Ale nadal dzieje się to **publicznie, jest proces RFC**, wdrożenie do wersji `canary` i dopiero dużo, dużo później w stabilnej wersji.

Problemem dla zespołu Remixa może być to, że **to nie oni, a team Next.js wykorzystał nowe feature'y Reacta jako pierwszy**. Mówimy tu o React Server Components i Server Actions – rzeczach, które nie są częścią samego Nexta, a właśnie Reacta i w zasadzie każdy metaframework może z nich korzystać. Remix świadomie i [celowo tego nie robi](https://remix.run/blog/react-server-components#remix-can-take-full-advantage-of-rsc), więc tym bardziej trudno zrozumieć ten punkt krytyki.

## Experimenting on my users

> Features that Next.js is shipping as stable are in the canary release of React.
Tak jest, dokładnie! Tylko, że określenie `canary` w React ma nieco [inne znaczenie niż w innych projektach](https://react.dev/blog/2023/05/03/react-canaries). Twórcy Reacta mówią, że przez `canary` rozumieją taką wersję React.js, która jest gotowa do adopcji w metaframeworkach. Wersja niestabilna Reacta oznaczona jest tagiem `experimental`. To chyba wyjaśnia wątpliwości podane w tym akapicie, prawda

## Too much magic

Wbrew temu co pisze Kent, Next.js w pełni zaadoptował Web API. Formularze są, cóż, zwykłymi `form`, API Route'y korzystają z `Request``Response`, jest ogromny nacisk na `fetch` i tak dalej… **Zdecydowanie mniej magii niż w poprzednich wersjach.**

Dla odmiany, Remix idzie w [zupełnie przeciwnym kierunku](https://twitter.com/ryanflorence/status/1686757173202997249):

- zamiast standardowego `async/server components` mają `loader`
- zamiast standardowego `server actions`, `"use server"` mają `action`
- zamiast standardowego `"use client"` mają `code splitting`
- zamiast standardowego `async components` mają SSR `ErrorBoundary`
- zamiast standardowego `async components` mają `defer`
- zamiast standardowego `<form action>` mają `Form`
- zamiast standardowego `useFormStatus`, `useOptimistic` mają `useNavigation`/`useFetcher`

I na razie nie planują tego zmieniać.

Bulwersujący dla wielu osób jest fakt, że Next.js nadpisuje funkcję `fetch` i dodaje do niej obsługę swojego cache. Kent porównuje to do nadpisywania prototypów wbudowanych obiektów przez MooTools, co jest albo zwykłą demagogią albo **całkowitym niezrozumieniem problemu.**

Dodawanie lub nadpisywanie własności w prototypach = problem z kompatybilnością i działaniem kodu pochodzącego z różnych źródeł.
Natomiast owinięcie globalnej funkcji w swój kod i wywołanie oryginału pod spodem nie stwarza problemów dla użytkowników końcowych. Co ciekawe, Next.js **nie ustanawia tu precedensu**. Choćby Angular nadpisuje praktycznie [wszystkie globalne funkcje](https://github.com/angular/angular/blob/HEAD/packages/zone.js/STANDARD-APIS.md#browser). Z ciekawszych przykładów warto wymienić też Cloudflare – jedną z firm zasiadających w grupie rozwijającej Web API – który w workerach nadpisuje `fetch` w sposób podobny do Nexta i również dodaje własną obsługę cache.

## Complexity

Autor pisze tutaj, że Next.js staje się zbyt skomplikowany, a jako przykłady tego podaje API dodane do React.js, z którego korzysta również Remix. Kurtyna.

[Ładnie skomentował to Dan Abramov](https://twitter.com/dan_abramov/status/1649214795571134465), więc chyba więcej dodawać nie trzeba.

## Stability

Jeśli dobrze rozumiem zamysł Kent C. Dodds, to teza tego akapitu jest następująca: „Remix miał tylko 2 wersje, więc jest bardziej stabilny niż Next.js, który miał 14 wersji”. **Jest to nie tylko absurdalne, ale też całkowicie nieprawdziwe.** Przyjrzyjmy się.

Next.js jest rozwijany od listopada 2016, a więc obchodzi 7 urodziny. Pierwszym wydaniem Next.js była wersja… `1.0.1`. Chcąc podążać za SemVer, od tego momentu każda modyfikacja publicznego API Next.js musiała wiązać się z podbiciem wersji `major`, czyli 1 na 2, 2 na 3 i tak dalej… Zwróćmy też uwagę, że wersja 13 została podbita do 14 tylko z uwagi na podniesienie wymaganej wersji Node.js – nie było innych _breaking changes_.

Remix przyjął odmienną strategię wersjonowania. Pierwsze publiczne wydanie oznaczone `0.8.0` ukazało się w 2021 roku. Wersje zaczynające się od `0.` zgodnie z SemVer mogą wprowadzać _breaking changes_ bez konieczności podbijania wersji `major`, a więc `0.8.0` zamienia się w `0.9.0`, a nie `1.0.0`. Poświęciłem więc czas na przejrzenie całego _changelogu_ Remixa i znalazłem tam **12 _breaking changes_**. Gdyby więc zespół Remixa przyjął ten sam model wersjonowania, byliby **po dwóch latach na tym samym numerze wersji 13 co Next.js po siedmiu**. Daje do myślenia, prawda?

Dodatkowo Kent zdaje się sugerować, że Next.js nie daje ścieżki migracji ze starych wersji do nowych – ale może to tylko moje złe wrażenie? Anyway, Next.js 12 na 13 migrujemy w 3 minuty – zmiany są malutkie, a nowy router całkowicie opcjonalny. Poprzedni sposób routingu będzie wspierany jeszcze długo, przez wiele lat. Z kolei migracja z 13 na 14 zajęła nam [w produkcyjnym projekcie dosłownie 30 sekund](https://twitter.com/zaiste/status/1717595898312642786).

## Podsumowanie

Rok temu [nagraliśmy z Zaiste film pod hasłem „Marketing Remixa obnażony”](https://www.youtube.com/live/XKeN9WsUAzM), w którym rozłożyliśmy na czynniki pierwsze porównanie Next.js i Remixa opublikowane przez zespół Remixa. Wykazaliśmy tam, że treści tam zawarte są **nie tylko nieobiektywnym stuntem marketingowym, ale po prostu zwykłymi kłamstwami i manipulacjami** bez grama wstydu.

Artykuł Kent C. Dodds jest nieco bardziej wyważony, jednak sentyment pozostaje ten sam: próbuje nam coś sprzedać, więc używa różnych nieprawdziwych, przeinaczonych, zmanipulowanych i subiektywnych informacji, które prezentuje jako fakty.

Ironiczne jest to, że Kent sprzedaje kurs Remixa na stronie napisanej w Next.js.

## Linki

1. [github.com/vercel/next.js/tree/canary/examples/with-docker-compose](https://github.com/vercel/next.js/tree/canary/examples/with-docker-compose)
2. [github.com/vercel/next.js/tree/canary/examples/with-docker-multi-env](https://github.com/vercel/next.js/tree/canary/examples/with-docker-multi-env)
3. [github.com/vercel/next.js/tree/canary/examples/with-docker](https://github.com/vercel/next.js/tree/canary/examples/with-docker)
4. [nextjs.org/docs/app/api-reference/next-config-js/incrementalCacheHandlerPath](https://nextjs.org/docs/app/api-reference/next-config-js/incrementalCacheHandlerPath)
5. [remix.run/blog/react-server-components#remix-can-take-full-advantage-of-rsc](https://remix.run/blog/react-server-components#remix-can-take-full-advantage-of-rsc)
6. [twitter.com/ryanflorence/status/1686757173202997249](https://twitter.com/ryanflorence/status/1686757173202997249)
7. [github.com/angular/angular/blob/HEAD/packages/zone.js/STANDARD-APIS.md#browser](https://github.com/angular/angular/blob/HEAD/packages/zone.js/STANDARD-APIS.md#browser)
8. [twitter.com/zaiste/status/1717595898312642786](https://twitter.com/zaiste/status/1717595898312642786)
9. [react.dev/blog/2023/05/03/react-canaries](https://react.dev/blog/2023/05/03/react-canaries)
10. [youtube.com/live/XKeN9WsUAzM](https://www.youtube.com/live/XKeN9WsUAzM)
4 changes: 2 additions & 2 deletions components/molecules/PromoWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ export const PromoWidget = memo(() => {
<li className="ml-0 pl-0 text-gray-700 text-lg font-semibold transition-colors">
<Link
className="hover:text-green-700"
href="https://www.next13masters.pl/webinar/?utm_source=typeofweb&utm_medium=sidebar"
href="https://www.next13masters.pl/?utm_source=typeofweb&utm_medium=sidebar"
>
Darmowy webinar: Next.js 13 wywraca wszystko do góry nogami. Zapisz się!
Już wkrótce odpalamy zapisy na drugą edycję next13masters.pl. Zapisz się na listę oczekujących!
</Link>
</li>
</ul>
Expand Down
7 changes: 3 additions & 4 deletions components/molecules/SiteHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,11 @@ export const SiteHeader = memo<{ readonly pageKind: PageKind }>(({ pageKind }) =
<LinkUnderlineEffect>
<a
className="text-blue-500 font-bold"
href="https://www.next13masters.pl/webinar/?utm_source=typeofweb&utm_medium=sidebar"
href="https://www.next13masters.pl/?utm_source=typeofweb&utm_medium=sidebar"
>
Darmowy webinar: Next.js 13 wywraca wszystko do góry nogami. Zapisz się!
Już wkrótce odpalamy zapisy na drugą edycję next13masters.pl. Zapisz się na listę oczekujących!
</a>
</LinkUnderlineEffect>{' '}
wkrótce dostępna!
</LinkUnderlineEffect>
</p>
</div>
</>
Expand Down
2 changes: 1 addition & 1 deletion generateFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Url from 'url';
import Bluebird from 'bluebird';
import { Feed } from 'feed';

import AuthorsJson from './authors.json';
import AuthorsJson from './authors.json' assert { type: 'json' };
import { siteName, defaultDescription } from './constants';
import { postToProps } from './utils/postToProps';
import { readAllPosts } from './utils/posts';
Expand Down
16 changes: 16 additions & 0 deletions oEmbedCache.json
Original file line number Diff line number Diff line change
Expand Up @@ -843,5 +843,21 @@
"html": "<iframe width=\"200\" height=\"113\" src=\"https://www.youtube.com/embed/LG7-Q3uCYUI?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>"
},
"updatedAt": 1641226257616
},
"https://twitter.com/dan_abramov/status/1649214795571134465": {
"data": {
"url": "https://twitter.com/dan_abramov/status/1649214795571134465",
"author_name": "danabra.mov",
"author_url": "https://twitter.com/dan_abramov",
"html": "<blockquote class=\"twitter-tweet\"><p lang=\"en\" dir=\"ltr\">it’s the other way around. Next has rewritten their entire framework under the technical direction and vision from the React team. including changing some APIs to something less Next-specific. we care a lot about this kind of stuff and getting it right.</p>&mdash; danabra.mov (@dan_abramov) <a href=\"https://twitter.com/dan_abramov/status/1649214795571134465?ref_src=twsrc%5Etfw\">April 21, 2023</a></blockquote>\n<script async src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>\n",
"width": 550,
"height": null,
"type": "rich",
"cache_age": "3153600000",
"provider_name": "Twitter",
"provider_url": "https://twitter.com",
"version": "1.0"
},
"updatedAt": 1698507153245
}
}
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "2.0.0",
"private": true,
"scripts": {
"dev": "./build.sh public && concurrently 'next dev' 'netlify-cms-proxy-server' --kill-others-on-fail",
"dev": "./build.sh public && next dev",
"dev2": "next-remote-watch ./_posts/**/*.mdx ./_pages/**/*.mdx ./_wordpress_posts/**/*.mdx ",
"build": "./build.sh feed public next",
"postbuild": "next-sitemap",
Expand All @@ -14,7 +14,7 @@
"algolia": "ENABLE_GITHUB_READ=false yarn ts-node-esm generateAlgoliaItems.ts",
"svg": "svgo --precision 1 -r -f {images,public}"
},
"type": "commonjs",
"type": "module",
"dependencies": {
"@algolia/recommend": "4.13.0",
"@giscus/react": "github:typeofweb/giscus-component#913a81c09ec80905c7ca5c166962ad547ddcc51c",
Expand Down Expand Up @@ -108,7 +108,7 @@
"svgo": "2.8.0",
"tailwindcss": "3.0.23",
"terser": "5.12.1",
"ts-node": "10.7.0",
"ts-node": "10.9.1",
"typescript": "4.6.3",
"unified": "10.1.2",
"unist-util-map": "3.0.1",
Expand All @@ -122,5 +122,9 @@
},
"browserslist": [
"> 2% in my stats"
]
],
"engines": {
"node": "^18.0.0",
"yarn": "^1.22.0"
}
}
2 changes: 1 addition & 1 deletion pages/[permalink]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AuthorsJson from '../../authors.json';
import AuthorsJson from '../../authors.json' assert { type: 'json' };
import { MDXComponent } from '../../components/MDXComponent';
import { Seo } from '../../components/Seo';
import { NewsletterForm } from '../../components/molecules/NewsletterForm';
Expand Down
2 changes: 1 addition & 1 deletion pages/[permalink]/strona/[page].tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AuthorsJson from '../../../authors.json';
import AuthorsJson from '../../../authors.json' assert { type: 'json' };
import { pageSize } from '../../../constants';
import { permalinkIsCategory } from '../../../utils/categories';
import { getMarkdownPostsFor, postToProps } from '../../../utils/postToProps';
Expand Down
8 changes: 7 additions & 1 deletion pages/api/admin-config.yml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,13 @@ function legacyWordpressCollection() {
}

async function posts() {
const authors = (await import(/* webpackChunkName: "authors" */ '../../authors.json')).default.authors;
const authors = (
await import(/* webpackChunkName: "authors" */ '../../authors.json', {
assert: {
type: 'json',
},
})
).default.authors;
return {
name: 'posts',
label: 'Artykuły',
Expand Down
2 changes: 1 addition & 1 deletion pages/api/oembed.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Authors from '../../authors.json';
import Authors from '../../authors.json' assert { type: 'json' };
import { getExcerptAndContent, getPostByPermalink } from '../../utils/posts';

import type { OembedData } from '../../utils/oEmbedCache';
Expand Down
4 changes: 2 additions & 2 deletions pages/api/relatedPosts2.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable functional/no-loop-statement -- useful */
import AuthorsJson from '../../authors.json';
import Data from '../../relatedPosts2.json';
import AuthorsJson from '../../authors.json' assert { type: 'json' };
import Data from '../../relatedPosts2.json' assert { type: 'json' };
import { postToProps } from '../../utils/postToProps';
import { getPostByPermalink } from '../../utils/posts';

Expand Down
2 changes: 1 addition & 1 deletion pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Fragment } from 'react';

import AuthorsJson from '../authors.json';
import AuthorsJson from '../authors.json' assert { type: 'json' };
import { Pagination } from '../components/atoms/Pagination';
import { NewsletterForm } from '../components/molecules/NewsletterForm';
import { ArticleSneakPeek } from '../components/organisms/ArticleSneakPeek';
Expand Down
2 changes: 1 addition & 1 deletion pages/kurs/[seriesSlug]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AuthorsJson from '../../../authors.json';
import AuthorsJson from '../../../authors.json' assert { type: 'json' };
import { getMarkdownPostsFor, getSeriesLinks, postToProps } from '../../../utils/postToProps';
import { getSeriesPermalinks } from '../../../utils/posts';
import { seriesSlugToSeries } from '../../../utils/series';
Expand Down
2 changes: 1 addition & 1 deletion pages/kurs/[seriesSlug]/strona/[page].tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AuthorsJson from '../../../../authors.json';
import AuthorsJson from '../../../../authors.json' assert { type: 'json' };
import { pageSize } from '../../../../constants';
import { getMarkdownPostsFor, postToProps } from '../../../../utils/postToProps';
import { getAllPermalinks, readAllPosts } from '../../../../utils/posts';
Expand Down
2 changes: 1 addition & 1 deletion pages/strona/[page].tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AuthorsJson from '../../authors.json';
import AuthorsJson from '../../authors.json' assert { type: 'json' };
import { pageSize } from '../../constants';
import { getMarkdownPostsFor, postToProps } from '../../utils/postToProps';
import { readAllPosts } from '../../utils/posts';
Expand Down
2 changes: 1 addition & 1 deletion prose.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
text-indent: 0;
}
.prose a {
@apply text-blue-500 no-underline font-bold tracking-tight;
@apply text-blue-500 no-underline font-bold tracking-tight word-break-break-word;
}
.prose strong {
color: theme('colors.gray.900');
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion utils/oEmbedCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Fsa from 'fs/promises';

import OembedParser from 'oembed-parser';
const { extract, setProviderList } = OembedParser;
import Providers from 'oembed-parser/src/utils/providers.json';
import Providers from 'oembed-parser/src/utils/providers.json' assert { type: 'json' };

import { host } from '../constants';

Expand Down
Loading

1 comment on commit 65c6137

@vercel
Copy link

@vercel vercel bot commented on 65c6137 Oct 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.