Show HN: Hacker News clone using Remix and React (github.com)

80 points by clintonwoo ↗ HN
Hi all, author here.

This project was a pleasure to do, Remix has very good developer experience. But further than that it's actually a really good way to develop applications, it's a mix of old meets new where the paradigm encourages you to take advantage of web standards for data fetching (forms, links, a tags).

And it turns out that it's actually an optimal way to develop simple or even complex web applications which can deploy in a number of runtime environments (including edge workers). So you can get an insanely fast website for users. Note that this project is not necessarily an optimal website implementation (since it copies Hacker News) but rather it's intended to be useful as a starting point or reference for your own projects!

You can read about some of the benefits of it on the project page linked.

By the way I'm currently available to work (if you email me at clinton.dannolfo @gmail!)

50 comments

[ 2.8 ms ] story [ 127 ms ] thread
(comment deleted)
Looking nice! I think Remix is gaining substantial popularity over Next.js these days. It's pretty interesting to watch.
This seems interesting! I'm not too familiar with Remix, could you expand on this bit:

> Most apps can be built leveraging web fundamentals (form/anchor tag) requiring no state management library

In general a client side web app might use something like Redux to hold state and make requests to the server using JavaScript (`fetch` for example). But with Remix you can just use HTML <form action="/submit" method="POST"> tags and have an `action`or `loader` which handles the response for it! This way the app even works when JS is disabled (Usually to redirect you to a new page after an action).

Remix also provides a higher level <Form> React component and hook with support for extra states like loading etc!

Hmmh, but what about global state for the 99% of users who do have JavaScript enabled? If I navigate to a page on a Remix app, and then hit the back button, does Remix do some trickery to maintain global state so that things such as scroll position are not broken?
Remix also supports client side React rendering so those redirects will be client side history and general routing logic applies just like using React Router. No page reloads will occur when using the Remix Form and Link component so the state is preserved according to regular React/React Router behaviour.
React Router isn't a state management library and doesn't provide global state. As far as I can tell, using Remix/React/React Router in this way would result in broken behavior. As an example, consider a news website that has infinite scroll on the front page. When the user clicks on an article and then clicks back, the user will see a version of the front page that is different from before, and their scroll position will be broken.
Remix has a ScrollRestoration component to handle the scroll position when going back and forward in the browser history.

When you click the browser back button as far as I remember Remix will do what the browsers do and show the previous data, but if you click a button in the UI to navigate to the previous page it will fetch the new data.

Re global state, in my experience with Remix and before it with tools like React Query, once you move the server state (data fetched from an API or queried from a DB) outside tools like Redux, then what you have left is mostly UI state (input values, open/close states, etc.) and you don't need Redux for that.

And if your app is more complex (like a canvas-like app for example) you can either use a state + context in a parent component or you can use Redux or any other state management library, Remix once JS load will not cause a full page navigation so if you initialize a global state in something like Remix it will keep working across page navigations, even if you click back it will keep the state because Remix uses RR to navigate so it's pure client-side navigation.

> When you click the browser back button as far as I remember Remix will do what the browsers do and show the previous data, but if you click a button in the UI to navigate to the previous page it will fetch the new data.

In this news site example the "previous data" was fetched with infinite scroll, and it was lost from memory when the user navigated to the new page. So when the user clicks back in their browser, the browser is unable to show the previous data, because it does not have the previous data in memory any more.

> Re global state, in my experience with Remix and before it with tools like React Query, once you move the server state (data fetched from an API or queried from a DB) outside tools like Redux, then what you have left is mostly UI state (input values, open/close states, etc.) and you don't need Redux for that.

> And if your app is more complex (like a canvas-like app for example) you can either use a state + context in a parent component or you can use Redux or any other state management library, Remix once JS load will not cause a full page navigation so if you initialize a global state in something like Remix it will keep working across page navigations, even if you click back it will keep the state because Remix uses RR to navigate so it's pure client-side navigation.

All this sounds like typical state management in any React app: keep local UI state in React components (no need need for a state management library) and use a state management library to maintain global state.

Your README gave the impression that Remix leverages web fundamentals in a way that removes/reduces the need for a state management library, but based on this discussion it sounds like that is not the case. Thanks for your answers and sorry about hijacking this thread for this.

To answer this question - There is nothing stopping you from fetching on the client still while using remix or from for example wrapping your app with a provider where you can store cached client side data.

Page transitions happen on the client when you have JS enabled so that data will still be in memory if done as such.

But I do want to mention that the specific use case you mention is not what 80%+ of apps are doing and I think you're being a bit unfair. Remix ABSOLUTELY does get rid of most needs for a client side state management solution. Most apps are fetching data and displaying it primarily on page load/route transitions. Or for instance on query param change for paginated data. This is the use case Remix targets, and it does a fantastic job of simplifying the code for this and making it much faster.

For those cases where you really do need to do client side data fetching you are free to do so.

> Remix ABSOLUTELY does get rid of most needs for a client side state management solution. Most apps are fetching data and displaying it primarily on page load/route transitions. Or for instance on query param change for paginated data. This is the use case Remix targets, and it does a fantastic job of simplifying the code for this and making it much faster.

But do these use cases require a state management library in the first place? Can you provide an example where using React Component state is not sufficient - a state management library is needed - and then Remix removes this need?

Remix co-author here. Infinite scroll/pagination is a great way to test the limits of a web framework!

Remix doesn't ship an infinite scroll/pagination set of components so it's up to apps to make sure that state is still there when the user clicks back. If the state is still there, Remix's scroll restoration will work.

You could either manage your own global state for this, but I like to use "location state" which is our API into the browser's built in `history.state`.

There are various ways to use it (declaratively, imperatively, etc.), but simplest way to explain is to imagine a "Load more" button that's really just a link like this:

`<Link to="?page=2" state={{ data }} />`

Your Remix loader would load page 2 from the url search param and your component would concat that onto `location.state.data` (the previous entries from the initial location). This renders the old data and the new data together.

Location state, unlike typical "global state", automatically persists across both refreshes and back/forward button clicks, but dies with the session, so scroll restoration will work as expected even for a refresh! Built in browser APIs tend to be a bit more resilient than storing stuff in application memory, they also keep your bundle smaller.

I don't know where the demo is, but I helped somebody at some point implement this without needing to ship a global state container for server-supplied data. Just made a note to make an example and put it our repo :)

If we're talking "load more" a much simpler way to do it is to consider how you'd do it old school with no JS. Just return all pages according to the search param, so "?page=3" would return all three pages. More kB over the network, but far easier to implement. There's even a product reason to do it this way: when you load more you automatically update the comment counts/points of each entry, so maybe it's worth it.

Great question!

> I like to use "location state" which is our API into the browser's built in `history.state` ... Location state, unlike typical "global state", automatically persists across both refreshes and back/forward button clicks, but dies with the session, so scroll restoration will work as expected even for a refresh! Built in browser APIs tend to be a bit more resilient than storing stuff in application memory, they also keep your bundle smaller.

This sounds great! I never thought of using history state to store arbitrary data. Thanks for the explanation!

For comparison this[1] is Next.js implementation that uses React Server Components (RSC). I feel folks at Remix missed on React Server Components and now it will be a lot of work to make it work in Remix. Hopefully I'm wrong.

If you don't know, RSC allows the HTML from server to start streaming as React is rendering the components in the backend. It allows fine grain control to what part of page renders first and which parts can be rendered later as HTML and data is streaming from the server. Before this, all of React server frameworks would render the entire page, shove "hydration data" in it which is mostly repeated data that is used to render the page and also include that HTML in the form of complied JSX in the page in the page scripts. Obviously that would make React SSR very bulky and unscalable. That's why RSC can solve so many React performance problems

[1] https://github.com/vercel/next-react-server-components

Here's an objective comparison that Ryan Florence (Remix co-creator) made showing a Remix Hacker News Clone (not the one posted, but one he built himself) vs the Next.js implementation you're talking about here: https://remix.run/blog/react-server-components

If React Server Components can improve things for Remix (they can't right now, but maybe they will get better?) and if it is ever released (it's been 5 years now... and they still are saying they've got a long way to go), then Remix will ship support for them and it will be a non-breaking change for Remix users.

I was coming to say something like this, also if Remix ever supports RSC it will most likely (IMO) be under noticed, like you just name a route `.server.tsx` and it will be a RSC and that's it, I don't think Remix will need a breaking change for this and RSC seems more like an implementation detail Remix has rather than something super big for the framework.
Hey Kent, could you please disclose that you work for Remix as well?
What's been 5 years? Server components were first shown off just over a year ago. If you mean suspense as a whole, then sure, it's been in the kitchen a long time.
Im not sure this is a fair comparison. The Nextjs implementation seems more like "let just get it running somehow". OP's codebase is looking scalable and maintainable. Any React developer would have a very easy time getting used to the code (first-glance-readable patterns and functions, uses Typescript etc...) whereas the Nextjs implementation uses very rare patterns and file structure for a Nextjs application (IMHO).

For example, where are the styles for the skeleton ? Why inserting CSS with "dangerouslySetInnerHTML" in the index.js ? Maybe its easy and I am missing some obvious stuff but the code doesnt seem very readable to me. Maybe is due to the nature of Server Components in Nextjs ? Would be nice to hear someone else's opinion on the patterns used in this repo.

OPs code on the other hand is a piece of cake to understand. Really good job!

Remix co-author here. We haven't "missed on RSC", they aren't even released!

We already have experimental versions of Remix running on React 18 and have experimented with RSC, Suspense, and streaming extensively. In their current state however, we're not seeing them beat Remix's current approach in production results or developer ergonomics. We've been providing the React team with our feedback. When these features from React are ready, Remix will be able to easily support them.

On my machines (desktop and smartphone), this implementation is noticeably faster than the real thing; or at least that's how I perceive it.

I never worked with, or even tried, React and such libraries but a bit surprised. The code in this version here must be much more complexe, and the server much smaller, yet (feels as if) it is faster.

Am I the only one experiencing this and surprised by it?

You're probably feeling it's faster because it's using <Link prefetch> on every link so everything is prefetched before you click the link (when you hover them) which makes the navigation almost instant once you click it.
The difference in speed is likely due to the fact that for many actions, data is sent as json and then placed into html markup on the client using code that was downloaded once at the beginning. This probably results in a smaller payload.
Yeah, it's due to client side routing. The tradeoff is slightly higher JS size on the first load (but it gets cached) then when performing navigations it's only needing to fetch the data required to render the page.

The alterative is an old school style web server that returns a new HTML file and page reload on every navigation which is higher download and runtime cost (which is how the real HN does it!). No criticism here as the site definitely works well regardless.

Remix (which uses React as the UI library) is server side rendered
True, but it still needs to download a big chunk of JS to have client-side routing.
Yes, though I wouldn't call it a 'big chunk'. The whole page was 500KB and it seems the Remix library code is a small piece of that (measuring in the 10s of KB).
Yeah, I didn't mean it was necessarily too big. I just meant that, due to the architecture of an app where React controls all the client-side routing and interactivity, you need a significant about of JS just to bootstrap the app even if you've only got a very small amount of client interactivity.
I see and that's accurate though this is where Remix really improves on React.

If you disable JS on the client and load a Remix app, the app will still work (though not as fully). You'll still be able to navigate links, load data etc. That's not true of most other apps built in just React that do all their data fetching client side and store all the data client side.

> That's not true of most other apps built in just React that do all their data fetching client side and store all the data client side.

I mean, it's true for any halfway competent implementation of a React app with React Router and SSR. I love Remix and I agree that it offers a great dev experience and will probably make developing React Router SSR apps much more accessible to a lot of new devs. But this particular aspect (SSR with nested routes that each have their own data loader function, and links that are actual <a href="" /> links) is, like, the bare minimum functionality you'd have in an SSR React app 5 years ago. I always feel like the Remix team is massively underselling itself when they focus so much on "we have SSR with nested routes!" instead of focusing on the real innovations that Remix brings to the table.

JS apps like this should be faster, since they are downloading the "shell" of the application once and simply rendering content from lightweight JSON data requests rather than requesting an entire HTML page and re-rendering it. (Which also requires the server to query and send back things the client already has, like your username and karma) Single page apps get a lot of hate here because they tend to end up as huge bloated applications (not that your typical website with ads and popups is any better), but it works great when done right.
Remix is a server-side rendering framework focused on pre-SPA web fundamentals like progressive enhancement and minimizing downloading and evaluating JavaScript and CSS assets for lighter, faster pages.

Edit: It does in-fact load a JSON payload along with client-side routing as well if you click to view comments. But then if you use browser refresh, it renders as a traditional server-side request. So true to its name, Remix is a blend of both approaches, playing on the strengths of each.

Yes, I should have mentioned that. Remix and Next remove the initial loading that SPAs seem to be known for, where the page is rendered but then it fetches again to actually get the content. I always hated that and was trying to find workarounds years ago for Angular because while loading JSON content is great for subsequent requests, it seemed so backwards to deliver the entire app over HTTP and then just make another HTTP request to fetch the content while the user looks at a loading screen, rather than doing it all in one step.
(comment deleted)
Might even be a nanosecond faster if they could drop the request to ?_data=routes%2F__main that fires off on every click and returns {}.
The author of this actually has a bunch of different HN clones with different JS tech:

https://news.ycombinator.com/from?site=github.com/clintonwoo

Might be fun to compare

Thanks for pointing that out! To add some extra info, the demo's are running on https://remix.hnclone.win (this project) https://hnclone.win (nextjs/graphql project)

It's not necessarily an apples to apples comparison of web servers though, since the NextJS one is also using GraphQL which adds extra response time. The remix one is also running on fly.io instead of a regular VPS in middle america just to cope with extra HN traffic.

Here's one built with Svelte: https://hn.svelte.dev/

It's not exactly an apples-to-apples comparison for a number of reasons including implementation and hosting differences, but might be interesting to people anyway. The code lives here: https://github.com/sveltejs/sites/tree/master/sites/hn.svelt...

I'm always interested to see which ones are running after a few months, or a year later when the time comes to pay to renew the domain.

Random yearly payments to keep tiny side project vanity URLs doesn't measure high on the life partner acceptance factor scale.

Huh, how cool. This may just be an odd coincidence but I have very photosensitive eyes so there are a few custom settings I cycle through whenever I need to reduce eye strain. Switched my settings back to mfg preset and for some reason the website is easier on my eyes than regular HN. The colors are all the same, the layout is the same, the zoom is the same - can't really figure out why but I thought that was neat.
On my machine the clone has more padding and line height, which makes it easier on the eyes
Great implementation, looks identical to HN and is very responsive! HN is a great website for trying out web frameworks, given how simple it is in terms of features and design. I made an alternative front end for HN not too long ago that was styled like the Windows XP desktop[0], was good fun. I wonder how many HN clones and front ends have been written for HN at this point, I'm guessing at least several hundred.

[0] https://hackerxp.com/

Only thing I notice is that the collapse button isn't a button and is the entire [-] thing, vs. just the - or + being an actual button.

Very great work! I love destroying the notion that you have to use some esoteric hyper-optimized tech to get quality performance.

Yes, the buttons and links here are horribly tiny on mobile.

I don't know the stats, but I could imagine that HN is used by quite some mobile users, so an UX update would be appreciated.

(comment deleted)
Really interesting. I’ve been wanting to try Remix out for a bit after heavily investing in Next.

Side note: seems like there is some sort of issue when swiping back on iOS Safari. Periodically getting flashes of the incorrect content.

I really hope someone can made this info a wordpress theme+plugin, then add some auto deleted thread after X times, then cool!