Enable Fast Refresh for remote data in NextJS.

Overview

next-remote-refresh

Utilize Fast Refresh for remote data in NextJS.

⚠️ This solution relies on undocumented APIs and may break in future NextJS updates.

Install

yarn add next-remote-refresh --dev
npm install next-remote-refresh --save-dev

Usage

plugin

Add and configure plugin in next.config.js:

// next.config.js
const withRemoteRefresh = require('next-remote-refresh')({
  paths: [require('path').resolve(__dirname, './package.json')],
  ignored: '**/*.json',
})

module.exports = withRemoteRefresh(nextConfig)

useRemoteRefresh hook

Add the useRemoteRefresh hook to the top-level component in your app. You may also configure when the app should refresh based on the changed path:

import { useRouter } from 'next/router'
import { useRemoteRefresh } from 'next-remote-refresh/hook'
import path from 'path'

function App({ name, version }) {
  const router = useRouter()
  useRemoteRefresh({
    shouldRefresh: (path) => path.includes(router.query.slug),
  })
  return (
    <div>
      Package: {name} Version: {version}
    </div>
  )
}

export function getStaticProps() {
  return {
    props: path.resolve(process.cwd(), './package.json', 'utf-8'),
  }
}

Development

yarn install && yarn link
cd example
yarn install && yarn link next-remote-refresh
yarn dev

Related

Refreshing Server-Side Props

next-remote-watch

Contributors

Thanks goes to these wonderful people (emoji key):


Travis Arnold

💻 📖

Joshua Comeau

🤔

Fatih Kalifa

💻 📖

Jason Brown

📖

Paco

💻

Arnav Gosain

💻

This project follows the all-contributors specification. Contributions of any kind welcome!

Comments
  • fix: wait for ws server to be initialized

    fix: wait for ws server to be initialized

    closes #29

    I also adapted the "Usage" section of the README to export an async function, as suggested by @ajitid in #29. I think this makes it clear enough that withRemoteRefresh is async, the types also represent that.

    opened by pkerschbaum 6
  • Error: Cannot destructure property 'port' of 'createServer(...).address(...)' as it is null.

    Error: Cannot destructure property 'port' of 'createServer(...).address(...)' as it is null.

    Problem

    withRemoteRefresh of next-remote-refresh sometimes throws an error:

    error - Failed to load next.config.js, see more info here https://nextjs.org/docs/messages/next-config-error
    Error: Cannot destructure property 'port' of 'createServer(...).address(...)' as it is null.
        at async Object.loadConfig [as default] (file:///home/projects/nextjs-y1ndyw/node_modules/next/dist/server/config.js:69:36)
        at async NextServer.prepare (file:///home/projects/nextjs-y1ndyw/node_modules/next/dist/server/next.js:130:24)
        at async eval (file:///home/projects/nextjs-y1ndyw/node_modules/next/dist/cli/next-dev.js:343:17)
    TypeError: Cannot read properties of null (reading 'close')
        at RoundRobinHandle.remove (https://nextjsy1ndyw-jwoi.w-credentialless.staticblitz.com/blitz.6ebe18b1e1db1833797e3e4eb6ff802c375ee61b.js:6:1290129)
        at https://nextjsy1ndyw-jwoi.w-credentialless.staticblitz.com/blitz.6ebe18b1e1db1833797e3e4eb6ff802c375ee61b.js:6:1425701
        at Map.forEach (<anonymous>)
        at removeHandlesForWorker (https://nextjsy1ndyw-jwoi.w-credentialless.staticblitz.com/blitz.6ebe18b1e1db1833797e3e4eb6ff802c375ee61b.js:6:1425682)
        at ChildProcess.<anonymous> (https://nextjsy1ndyw-jwoi.w-credentialless.staticblitz.com/blitz.6ebe18b1e1db1833797e3e4eb6ff802c375ee61b.js:6:1427512)
        at Object.onceWrapper (https://nextjsy1ndyw-jwoi.w-credentialless.staticblitz.com/blitz.6ebe18b1e1db1833797e3e4eb6ff802c375ee61b.js:6:152909)
        at EventEmitter.emit (https://nextjsy1ndyw-jwoi.w-credentialless.staticblitz.com/blitz.6ebe18b1e1db1833797e3e4eb6ff802c375ee61b.js:6:155574)
        at finish (https://nextjsy1ndyw-jwoi.w-credentialless.staticblitz.com/blitz.6ebe18b1e1db1833797e3e4eb6ff802c375ee61b.js:6:963993)
        at _0x19cf18 (https://nextjsy1ndyw-jwoi.w-credentialless.staticblitz.com/blitz.6ebe18b1e1db1833797e3e4eb6ff802c375ee61b.js:15:148934)
        at https://nextjsy1ndyw-jwoi.w-credentialless.staticblitz.com/blitz.6ebe18b1e1db1833797e3e4eb6ff802c375ee61b.js:15:148682
    

    Minimal reproduction example: https://stackblitz.com/edit/nextjs-y1ndyw

    I have used next-remote-refresh successfully in the past for my homepage project, but suddenly the error occurs.

    While I was not able to find out which change in my project did introduce this problem, I think I figured out the underlying problem:
    Here, next-remote-refresh creates a websocket server and immediately tries to read the address from it:

    https://github.com/souporserious/next-remote-refresh/blob/f934f4883d2a0794b96ba37e23cc88e50cd9e624/index.js#L9

    It is using WebSocketServer.address() of the package ws, which is invoking http.Server.address() from Node.js:

    github.com/websockets/ws/lib/websocket-server.js#L129-L145

    And here's the thing: as is stated in the Node.js net server.address() docs:

    server.address() returns null before the 'listening' event has been emitted or after calling server.close().

    Solution

    Wait for the listening event before reading the port from the server.
    I created a patch with pnpm which I successfully apply in my project, see below. It makes the "withConfig" function async unfortunately, but when using next.config.mjs and a Node version supporting top-level-await, this is not a huge issue (as you can see here: github.com/pkerschbaum/pkerschbaum-homepage/packages/apps/web/next.config.js#L50).

    diff --git a/index.d.ts b/index.d.ts
    index 292d05f918836563e1e5d671ae3046a0b257a1a0..943d1eaca68dcb4e9471e8d2e35aeaef2afa1374 100644
    --- a/index.d.ts
    +++ b/index.d.ts
    @@ -6,4 +6,4 @@ interface PluginOptions {
       signal?: AbortSignal
     }
     
    -export default function plugin (options: PluginOptions): (nextConfig: NextConfig) => NextConfig
    +export default function plugin (options: PluginOptions): (nextConfig: NextConfig) => Promise<NextConfig>
    diff --git a/index.js b/index.js
    index 811c02988758a45975eec5352689b8496974368a..ca557ed5a15eef0e6c1d91ef6651300bd20499ae 100644
    --- a/index.js
    +++ b/index.js
    @@ -3,10 +3,15 @@ const createServer = require('./server')
     module.exports = function plugin(options) {
       let port
     
    -  return function withConfig(nextConfig = {}) {
    +  return async function withConfig(nextConfig = {}) {
         if (process.env.NODE_ENV !== 'production') {
    +      const server = createServer(options)
    +      await new Promise((resolve, reject) => {
    +        server.on('listening', resolve)
    +        server.on('error', reject)
    +      })
           if (port === undefined) {
    -        ({ port } = createServer(options).address())
    +        ({ port } = server.address())
           }
     
           if (nextConfig.env === undefined) {
    
    opened by pkerschbaum 3
  • Prevent scrolling to the top of the page

    Prevent scrolling to the top of the page

    Thanks for making this package, it's super useful!

    I noticed that the auto refresh would scroll to the top of the page, so this just makes it stay at the same position.

    opened by maggie-j-liu 3
  • Fix optional shouldRefresh hook param

    Fix optional shouldRefresh hook param

    I missed this while refactoring the hook in the last PR. I'm avoiding assigning a default value in the hook's arguments since it would case the useEffect to run every render.

    opened by kherock 3
  • Fix type definition and reloads after route changes

    Fix type definition and reloads after route changes

    Hi! Thanks for the library, it works great aside from some of the client side logic that I've fixed here.

    I also fixed the type definitions. I'm not sure how they were working before, the import at the top of the file causes TS to parse it as a module which prevents it from creating new module definitions in the global namespace. I've split it into two files for index.d.ts and hooks.d.ts since I think those are easier to maintain.

    The other issue I was seeing is that the router.replace call in the hook was only ever reloading the route that the hook was initialized with. I separated the websocket initialization step into its own useEffect hook that only runs once. I modified the existing useEffect to update every time router.asPath changes and made sure that the hook cleans up after itself with removeEventListener in the destructor.

    opened by kherock 3
  • Breaks in Next.js 12

    Breaks in Next.js 12

    I have read the readme mentioning about this might break in future version of Next.js. Next.js 12 was released today and it breaks. Is there a way to fix this?

    image

    opened by collegewap 3
  • add unit tests and CI jobs

    add unit tests and CI jobs

    Hi again and happy new year!

    I was able to make some time to give this library some test coverage and threw together a couple of simple workflow jobs with GH Actions as well.

    When you have the time, I wanted to bring your attention to a couple things

    • 👍 or 👎 on the StandardJS ESLint config? The existing code didn't need any changes aside from the whitespace after the function name with their config.
    • ~~I've added a signal prop to the server options that allows tests to clean up after themselves with an AbortSignal. This doesn't break Node 12 compatibility[^1] since it's optional and doesn't rely on any global objects.~~ I've realized that this is unnecessary if I modify createServer to simply return a handle to the websocket server instance
    • the parsing of the filePath passed to the chokidar callback is buggy when the changed file doesn't share a common ancestor with the working directory.
      • I think it's already a bit problematic that working directory basename is preprended to the base path since it makes equality checks from shouldRefresh more difficult to write. I think a better solution would be to pass a cwd to chokidar and just forward the paths emittered from there directly
      • my unit tests actually struggle with this since they write files to the /tmp directory when my workspace directory is in my home folder, so I'd like to include a fix to this in this PR if that's ok
    • file creation/deletion doesn't trigger a refresh when it probably should

    [^1]: AbortController/AbortSignal only has native support since 14.17

    opened by kherock 2
  • implement next plugin with SSE

    implement next plugin with SSE

    • Implement next plugin that spawn http server on dev
    • Rewrite server to use SSE using vanilla http module to reduce dependencies
    • Rewrite useRemoteRefresh hook to use internal next.js EventSource, this allows us to trigger next.js loading UI by encoding specific payload in SSE
    • Remove hardcoded port in both server & client by relying on auto generated port in server + publicRuntimeConfig in client

    Removed

    • bin/next-remote-refresh as it's no longer used. This is technically breaking but we're still < v1.x.x.
    • router arg in shouldRefresh callback due to stale router issue. IMO it's better for user to use their own useRouter if needed
    opened by pveyes 2
  • Avoid emitting events during the initial directory scan

    Avoid emitting events during the initial directory scan

    On server startup, next-remote-refresh logs all of the files matched in the paths glob(s) due to chokidar's default behavior of emitting add/addDir for all of the files it discovers. Setting this configuration to true lets us avoid a bunch of unnecessary console spam.

    opened by kherock 1
  • feat: add plugin

    feat: add plugin

    Trying to figure out a way to make setup easier by creating a plugin, ideally we can just do this:

    const withRemoteRefresh = require('next-remote-refresh')({
      paths: ['path', 'to', 'files'],
      port: 4001,
    })
    module.exports = withRemoteRefresh()
    

    Not sure this is viable though? Guessing that creating an additional server as a side-effect isn't a good idea 😅

    opened by souporserious 1
  • Misc updates

    Misc updates

    This PR contains the aforementioned fixes to the paths created by the file watcher. I'm passing cwd to chokidar to make it return paths relative to process.cwd(). These paths are then sent as-is to any connected clients.

    This is a breaking change in that the basename of the working directory is no longer prepended - I think this is necessary because the old strategy was incompatible with paths which share no common ancestor with the working directory. It also makes equality checks simpler on the front end (for me at least - in my case I currently have to strip out any leading ../ and check for a match with .endsWith()).

    opened by kherock 0
  • auto add useRemoteRefresh hook

    auto add useRemoteRefresh hook

    Just the start of this PR, not sure if it's viable or not. The current state of this is grabbing the wrong _app file. I don't know enough about NextJS internals for this, but thinking we can detect if an _app file is present and inject the hook or construct our own _app wrapper. An AST transform is probably better here, but wanted minimal performance impact if possible.

    closes #6

    opened by souporserious 0
  • Webpack loader

    Webpack loader

    Ideally, we should only need to set up the plugin and not worry about the hook. I'm thinking we can create a custom loader that injects the hook into the app:

    // index.js
    config.module.rules.unshift({
      test: /\.(js|jsx|ts|tsx)$/,
      use: [{ loader: path.resolve(__dirname, 'loader') }],
    })
    
    // loader.js
    module.exports = async function (source) {
      const callback = this.async()
      if (this.resourcePath.includes('_app')) {
        // insert hook here
      }
      return callback(null, source)
    }
    

    I don't have much time for this right now so a good first issue if someone wants to take this!

    help wanted 
    opened by souporserious 0
Owner
Travis Arnold
Designer 🎨 Engineer 📐 Systems 🎛 React ⚛️ He/Him
Travis Arnold
Shopify app using NextJS. No custom NextJS server needed.

Shopify NextJS App Example An example app built with NextJS that can be setup and deployed to production in seconds on Vercel. All the glory goes back

reda 35 Nov 29, 2022
A simple project to refresh on the usage of js canvas and getContext('2d') to create four interactive squares on the canvas when hovered changes color.

A simple project to refresh on the usage of js canvas and getContext('2d') to create four interactive squares on the canvas when hovered changes color. Can also be clicked to work on mobile devices.

DandaIT04 1 Jan 1, 2022
React Hooks library for remote data fetching

Introduction swr.vercel.app SWR is a React Hooks library for remote data fetching. The name “SWR” is derived from stale-while-revalidate, a cache inva

Vercel 25.2k Jan 4, 2023
A portfolio built in React and NextJS. Simple, clean, and fast.

Personal Portfolio A portfolio built in React and NextJS. Simple, clean and fast. Note: The logo and banner used in the project are my intellectual pr

Vipul Jha 98 Jan 2, 2023
Dynamically set remote origins at runtime within hosts

external-remotes-plugin Host webpack.config const config = { ...otherConfigs plugins: [ new ModuleFederationPlugin({ name: "app1",

Module Federation 42 Nov 25, 2022
This is my portfolio GitHub clone website. The frontend is build using NextJS and TailwindCSS.

Github Clone Portfolio Website Tech Stack used: NextJS Tailwind CSS The contact form in this website is connected to Notion. If you want to integrate

Unnati Bamania 22 Oct 5, 2022
Nextjs CRUD with React Context API and Tailwind CSS

This is a Next.js project bootstrapped with create-next-app. Getting Started First, run the development server: npm run dev # or yarn dev Open http://

Fazt Web 23 Sep 25, 2022
nextjs-multiple-page-i18n

This is a Next.js project bootstrapped with create-next-app. Getting Started First, run the development server: npm run dev # or yarn dev Open http://

Mahabub Islam Prio 1 Sep 10, 2022
A basic React/NextJS project showing how to use the Flow Client Library (FCL)

How to use the Flow Client Library (FCL) with SvelteKit Everything you need to build a SvelteKit project with the Flow Client Library (FCL). For a Sve

Andrea Muttoni 19 Sep 24, 2022
Clone da GUI do Windows 11 desenvolvida utilizando ReactJS, NextJS, MaterialUI e ReactDND.

Vídeo demonstração Iniciando a execução: npm run dev Frameworks & Bibliotecas: React.js - uma biblioteca JavaScript para a criação de interfaces de us

nicolle 101 Dec 19, 2022
This is a movie app project using imdb database created by nextjs and tailwind css.

This is a Next.js project bootstrapped with create-next-app. Getting Started First, run the development server: npm run dev # or yarn dev Open http://

Sahand Ghavidel 1 Dec 24, 2021
Twitter-Clone-Nextjs - Twitter Clone Built With React JS, Next JS, Recoil for State Management and Firebase as Backend

Twitter Clone This is a Next.js project bootstrapped with create-next-app. Getting Started First, run the development server: npm run dev # or yarn de

Basudev 0 Feb 7, 2022
Boilerplate for a NextJS and Supabase Project. Including full authentication with sign up, login and password recovery.

NextJS x Supabase Boilerplate Getting Started Environment variables Create the enviroment variables with the following command cp .env.local.dist .env

Dominik Amrugiewicz 9 Jun 13, 2022
Boilerplate de projeto nextjs, com Styled Components e Typescript

This is a Next.js project bootstrapped with create-next-app. Getting Started First, run the development server: npm run dev # or yarn dev Open http://

Fabi Rodrigues 2 Mar 23, 2022
NextJs + Spring Boot + MySQL

iMusic Website View Demo Description This is a website to liste to music online build with backend Spring Boot, database MySQL, and frontend Nextjs. P

 Phạm Dung Bắc 15 Nov 20, 2022
Personal website and blog made using NextJS, TailwindCSS, GraphCMS, and MDX bundler

This is a Next.js project bootstrapped with create-next-app. Getting Started First, run the development server: npm run dev # or yarn dev Open http://

null 12 Aug 21, 2022
This repository store the source code of a chat application built in NextJS.

This repository store the source code of a chat application built in NextJS. The code was built in alura's React event, and here I styled the code to make it unique and creative!

Victor Silva 5 Mar 31, 2022
A simple pokedéx build with Nextjs and PokéAPI

Pokédex A simple pokedéx build with Nextjs and PokéAPI View Demo · Report Bug · Request Feature Table of Content About The Project Built With Getting

Louis 6 Dec 27, 2022
A simple showcase of how to create a CI/CD to automate nextjs deploy to github pages.

TypeScript & Styled Components Next.js example This is a really simple project that show the usage of Next.js with TypeScript and Styled Components. E

Pedro Frattezi SIlva 3 Apr 27, 2022