diff --git a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts
index 70a3ae3596ef74e..78bb79ab5903cd2 100644
--- a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts
+++ b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts
@@ -45,7 +45,14 @@ export function getRender({
const renderServerComponentData = isServerComponent
? query.__flight__ !== undefined
: false
+
+ const serverComponentProps =
+ isServerComponent && query.__props__
+ ? JSON.parse(query.__props__)
+ : undefined
+
delete query.__flight__
+ delete query.__props__
const req = {
url: pathname,
@@ -73,6 +80,7 @@ export function getRender({
supportsDynamicHTML: true,
concurrentFeatures: true,
renderServerComponentData,
+ serverComponentProps,
serverComponentManifest: isServerComponent ? rscManifest : null,
ComponentMod: null,
}
diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx
index 85a902d32443f9e..e7ab94e6b57a3eb 100644
--- a/packages/next/client/index.tsx
+++ b/packages/next/client/index.tsx
@@ -1,6 +1,6 @@
/* global location */
import '@next/polyfill-module'
-import React from 'react'
+import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import { StyleRegistry } from 'styled-jsx'
import { HeadManagerContext } from '../shared/lib/head-manager-context'
@@ -35,6 +35,7 @@ import { RouteAnnouncer } from './route-announcer'
import { createRouter, makePublicRouterInstance, useRouter } from './router'
import isError from '../lib/is-error'
import { trackWebVitalMetric } from './vitals'
+import { RefreshContext } from './rsc'
///
@@ -643,12 +644,30 @@ const wrapApp =
let RSCComponent: (props: any) => JSX.Element
if (process.env.__NEXT_RSC) {
+ const {
+ createFromFetch,
+ } = require('next/dist/compiled/react-server-dom-webpack')
function createResponseCache() {
return new Map()
}
const rscCache = createResponseCache()
+ function fetchFlight(href: string, props?: any) {
+ const url = new URL(href, location.origin)
+ const searchParams = url.searchParams
+ searchParams.append('__flight__', '1')
+ if (props) {
+ searchParams.append('__props__', JSON.stringify(props))
+ }
+ return fetch(url.toString())
+ }
+
+ const getHref = () => {
+ const { pathname, search } = location
+ return pathname + search
+ }
+
const RSCWrapper = ({
cacheKey,
serialized,
@@ -658,12 +677,8 @@ if (process.env.__NEXT_RSC) {
serialized?: string
_fresh?: boolean
}) => {
- const {
- createFromFetch,
- } = require('next/dist/compiled/react-server-dom-webpack')
let response = rscCache.get(cacheKey)
- // If there is no cache, or there is serialized data already
if (!response) {
response = createFromFetch(
serialized
@@ -672,16 +687,10 @@ if (process.env.__NEXT_RSC) {
t.writable.getWriter().write(new TextEncoder().encode(serialized))
return Promise.resolve({ body: t.readable })
})()
- : (() => {
- const { search, pathname } = location
- const flightReqUrl =
- pathname + search + (search ? '&' : '?') + '__flight__'
- return fetch(flightReqUrl)
- })()
+ : fetchFlight(getHref())
)
rscCache.set(cacheKey, response)
}
-
const root = response.readRoot()
return root
}
@@ -689,14 +698,31 @@ if (process.env.__NEXT_RSC) {
RSCComponent = (props: any) => {
const cacheKey = useRouter().asPath
const { __flight_serialized__, __flight_fresh__ } = props
+ const [, dispatch] = useState({})
+ // @ts-ignore TODO: remove when react 18 types are supported
+ const startTransition = React.startTransition
+ const renrender = () => dispatch({})
+ // If there is no cache, or there is serialized data already
+ function refreshCache(nextProps: any) {
+ startTransition(() => {
+ const href = getHref()
+ const response = createFromFetch(fetchFlight(href, nextProps))
+ // FIXME: router.asPath can be different from current location due to navigation
+ rscCache.set(href, response)
+ renrender()
+ })
+ }
+
return (
-
-
-
+
+
+
+
+
)
}
}
diff --git a/packages/next/client/rsc.ts b/packages/next/client/rsc.ts
new file mode 100644
index 000000000000000..7caa504a4deb315
--- /dev/null
+++ b/packages/next/client/rsc.ts
@@ -0,0 +1,7 @@
+import { createContext, useContext } from 'react'
+
+export const RefreshContext = createContext((_: any) => {})
+
+export function useRefreshRoot() {
+ return useContext(RefreshContext)
+}
diff --git a/packages/next/rsc.d.ts b/packages/next/rsc.d.ts
new file mode 100644
index 000000000000000..1e63004efc5fa18
--- /dev/null
+++ b/packages/next/rsc.d.ts
@@ -0,0 +1 @@
+export * from './dist/client/rsc'
diff --git a/packages/next/rsc.js b/packages/next/rsc.js
new file mode 100644
index 000000000000000..792373906a60470
--- /dev/null
+++ b/packages/next/rsc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ unstable_useRefreshRoot: require('./dist/client/rsc').useRefreshRoot,
+}
diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx
index 927e237a72a3a3b..56ae6780be9e312 100644
--- a/packages/next/server/render.tsx
+++ b/packages/next/server/render.tsx
@@ -209,6 +209,7 @@ export type RenderOptsPartial = {
resolvedAsPath?: string
serverComponentManifest?: any
renderServerComponentData?: boolean
+ serverComponentProps?: any
distDir?: string
locale?: string
locales?: string[]
@@ -356,6 +357,7 @@ export async function renderToHTML(
getServerSideProps,
serverComponentManifest,
renderServerComponentData,
+ serverComponentProps,
isDataReq,
params,
previewProps,
@@ -1004,7 +1006,7 @@ export async function renderToHTML(
if (renderServerComponentData) {
const stream: ReadableStream = renderToReadableStream(
- ,
+ ,
serverComponentManifest
)
const reader = stream.getReader()