diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx
index e846b283192d2..8241a25789448 100644
--- a/packages/next/src/server/app-render/app-render.tsx
+++ b/packages/next/src/server/app-render/app-render.tsx
@@ -467,6 +467,7 @@ export const renderToHTMLOrFlight: AppPageRender = (
                 href={fullHref}
                 // @ts-ignore
                 precedence={precedence}
+                crossOrigin={renderOpts.crossOrigin}
                 key={index}
               />
             )
@@ -511,7 +512,7 @@ export const renderToHTMLOrFlight: AppPageRender = (
             const ext = /\.(woff|woff2|eot|ttf|otf)$/.exec(fontFilename)![1]
             const type = `font/${ext}`
             const href = `${assetPrefix}/_next/${fontFilename}`
-            ComponentMod.preloadFont(href, type)
+            ComponentMod.preloadFont(href, type, renderOpts.crossOrigin)
           }
         } else {
           try {
@@ -546,7 +547,7 @@ export const renderToHTMLOrFlight: AppPageRender = (
             const precedence =
               process.env.NODE_ENV === 'development' ? 'next_' + href : 'next'
 
-            ComponentMod.preloadStyle(fullHref)
+            ComponentMod.preloadStyle(fullHref, renderOpts.crossOrigin)
 
             return (
               <link
@@ -554,6 +555,7 @@ export const renderToHTMLOrFlight: AppPageRender = (
                 href={fullHref}
                 // @ts-ignore
                 precedence={precedence}
+                crossOrigin={renderOpts.crossOrigin}
                 key={index}
               />
             )
@@ -1449,21 +1451,26 @@ export const renderToHTMLOrFlight: AppPageRender = (
         tree: LoaderTree
         formState: any
       }) => {
-        const polyfills = buildManifest.polyfillFiles
-          .filter(
-            (polyfill) =>
-              polyfill.endsWith('.js') && !polyfill.endsWith('.module.js')
-          )
-          .map((polyfill) => ({
-            src: `${assetPrefix}/_next/${polyfill}${getAssetQueryString(
-              false
-            )}`,
-            integrity: subresourceIntegrityManifest?.[polyfill],
-          }))
+        const polyfills: JSX.IntrinsicElements['script'][] =
+          buildManifest.polyfillFiles
+            .filter(
+              (polyfill) =>
+                polyfill.endsWith('.js') && !polyfill.endsWith('.module.js')
+            )
+            .map((polyfill) => ({
+              src: `${assetPrefix}/_next/${polyfill}${getAssetQueryString(
+                false
+              )}`,
+              integrity: subresourceIntegrityManifest?.[polyfill],
+              crossOrigin: renderOpts.crossOrigin,
+              noModule: true,
+              nonce,
+            }))
 
         const [preinitScripts, bootstrapScript] = getRequiredScripts(
           buildManifest,
           assetPrefix,
+          renderOpts.crossOrigin,
           subresourceIntegrityManifest,
           getAssetQueryString(true),
           nonce
@@ -1533,15 +1540,7 @@ export const renderToHTMLOrFlight: AppPageRender = (
                 {polyfillsFlushed
                   ? null
                   : polyfills?.map((polyfill) => {
-                      return (
-                        <script
-                          key={polyfill.src}
-                          src={polyfill.src}
-                          integrity={polyfill.integrity}
-                          noModule={true}
-                          nonce={nonce}
-                        />
-                      )
+                      return <script key={polyfill.src} {...polyfill} />
                     })}
                 {renderServerInsertedHTML()}
                 {errorMetaTags}
@@ -1651,6 +1650,7 @@ export const renderToHTMLOrFlight: AppPageRender = (
             getRequiredScripts(
               buildManifest,
               assetPrefix,
+              renderOpts.crossOrigin,
               subresourceIntegrityManifest,
               getAssetQueryString(false),
               nonce
diff --git a/packages/next/src/server/app-render/required-scripts.tsx b/packages/next/src/server/app-render/required-scripts.tsx
index d8da09d9c016c..e686ba326c3a3 100644
--- a/packages/next/src/server/app-render/required-scripts.tsx
+++ b/packages/next/src/server/app-render/required-scripts.tsx
@@ -5,13 +5,25 @@ import ReactDOM from 'react-dom'
 export function getRequiredScripts(
   buildManifest: BuildManifest,
   assetPrefix: string,
+  crossOrigin: string | undefined,
   SRIManifest: undefined | Record<string, string>,
   qs: string,
   nonce: string | undefined
-): [() => void, string | { src: string; integrity: string }] {
+): [
+  () => void,
+  { src: string; integrity?: string; crossOrigin?: string | undefined }
+] {
   let preinitScripts: () => void
   let preinitScriptCommands: string[] = []
-  let bootstrapScript: string | { src: string; integrity: string } = ''
+  const bootstrapScript: {
+    src: string
+    integrity?: string
+    crossOrigin?: string | undefined
+  } = {
+    src: '',
+    crossOrigin,
+  }
+
   const files = buildManifest.rootMainFiles
   if (files.length === 0) {
     throw new Error(
@@ -19,10 +31,9 @@ export function getRequiredScripts(
     )
   }
   if (SRIManifest) {
-    bootstrapScript = {
-      src: `${assetPrefix}/_next/` + files[0] + qs,
-      integrity: SRIManifest[files[0]],
-    }
+    bootstrapScript.src = `${assetPrefix}/_next/` + files[0] + qs
+    bootstrapScript.integrity = SRIManifest[files[0]]
+
     for (let i = 1; i < files.length; i++) {
       const src = `${assetPrefix}/_next/` + files[i] + qs
       const integrity = SRIManifest[files[i]]
@@ -34,12 +45,14 @@ export function getRequiredScripts(
         ReactDOM.preinit(preinitScriptCommands[i], {
           as: 'script',
           integrity: preinitScriptCommands[i + 1],
+          crossOrigin,
           nonce,
         })
       }
     }
   } else {
-    bootstrapScript = `${assetPrefix}/_next/` + files[0] + qs
+    bootstrapScript.src = `${assetPrefix}/_next/` + files[0] + qs
+
     for (let i = 1; i < files.length; i++) {
       const src = `${assetPrefix}/_next/` + files[i] + qs
       preinitScriptCommands.push(src)
@@ -50,6 +63,7 @@ export function getRequiredScripts(
         ReactDOM.preinit(preinitScriptCommands[i], {
           as: 'script',
           nonce,
+          crossOrigin,
         })
       }
     }
diff --git a/packages/next/src/server/app-render/rsc/preloads.ts b/packages/next/src/server/app-render/rsc/preloads.ts
index 6aae78ac668a4..fdb2bc39bdcc0 100644
--- a/packages/next/src/server/app-render/rsc/preloads.ts
+++ b/packages/next/src/server/app-render/rsc/preloads.ts
@@ -6,18 +6,29 @@ Files in the rsc directory are meant to be packaged as part of the RSC graph usi
 
 import ReactDOM from 'react-dom'
 
-export function preloadStyle(href: string) {
-  ReactDOM.preload(href, { as: 'style' })
-}
-
-export function preloadFont(href: string, type: string) {
-  ;(ReactDOM as any).preload(href, { as: 'font', type })
+export function preloadStyle(href: string, crossOrigin?: string | undefined) {
+  const opts: any = { as: 'style' }
+  if (typeof crossOrigin === 'string') {
+    opts.crossOrigin = crossOrigin
+  }
+  ReactDOM.preload(href, opts)
 }
 
-export function preconnect(href: string, crossOrigin?: string) {
+export function preloadFont(
+  href: string,
+  type: string,
+  crossOrigin?: string | undefined
+) {
+  const opts: any = { as: 'font', type }
   if (typeof crossOrigin === 'string') {
-    ;(ReactDOM as any).preconnect(href, { crossOrigin })
-  } else {
-    ;(ReactDOM as any).preconnect(href)
+    opts.crossOrigin = crossOrigin
   }
+  ReactDOM.preload(href, opts)
+}
+
+export function preconnect(href: string, crossOrigin?: string | undefined) {
+  ;(ReactDOM as any).preconnect(
+    href,
+    typeof crossOrigin === 'string' ? { crossOrigin } : undefined
+  )
 }
diff --git a/packages/next/src/server/app-render/types.ts b/packages/next/src/server/app-render/types.ts
index 1d83b5f417a77..085b63629b4ca 100644
--- a/packages/next/src/server/app-render/types.ts
+++ b/packages/next/src/server/app-render/types.ts
@@ -101,7 +101,7 @@ export type ChildProp = {
   segment: Segment
 }
 
-export type RenderOptsPartial = {
+export interface RenderOptsPartial {
   err?: Error | null
   dev?: boolean
   buildId: string
@@ -111,6 +111,7 @@ export type RenderOptsPartial = {
   runtime?: ServerRuntime
   serverComponents?: boolean
   assetPrefix?: string
+  crossOrigin?: '' | 'anonymous' | 'use-credentials' | undefined
   nextFontManifest?: NextFontManifest
   isBot?: boolean
   incrementalCache?: import('../lib/incremental-cache').IncrementalCache
diff --git a/test/e2e/app-dir/app-config-crossorigin/app/layout.js b/test/e2e/app-dir/app-config-crossorigin/app/layout.js
new file mode 100644
index 0000000000000..803f17d863c8a
--- /dev/null
+++ b/test/e2e/app-dir/app-config-crossorigin/app/layout.js
@@ -0,0 +1,7 @@
+export default function RootLayout({ children }) {
+  return (
+    <html>
+      <body>{children}</body>
+    </html>
+  )
+}
diff --git a/test/e2e/app-dir/app-config-crossorigin/app/page.js b/test/e2e/app-dir/app-config-crossorigin/app/page.js
new file mode 100644
index 0000000000000..84e7f049d5539
--- /dev/null
+++ b/test/e2e/app-dir/app-config-crossorigin/app/page.js
@@ -0,0 +1,3 @@
+export default function Index(props) {
+  return <p id="title">IndexPage</p>
+}
diff --git a/test/e2e/app-dir/app-config-crossorigin/index.test.ts b/test/e2e/app-dir/app-config-crossorigin/index.test.ts
new file mode 100644
index 0000000000000..a0d08fc24e12c
--- /dev/null
+++ b/test/e2e/app-dir/app-config-crossorigin/index.test.ts
@@ -0,0 +1,37 @@
+import { createNextDescribe } from 'e2e-utils'
+
+createNextDescribe(
+  'app dir - crossOrigin config',
+  {
+    files: __dirname,
+    skipDeployment: true,
+  },
+  ({ next, isNextStart }) => {
+    if (isNextStart) {
+      it('skip in start mode', () => {})
+      return
+    }
+    it('should render correctly with assetPrefix: "/"', async () => {
+      const $ = await next.render$('/')
+      // Only potential external (assetPrefix) <script /> and <link /> should have crossorigin attribute
+      $(
+        'script[src*="https://example.vercel.sh"], link[href*="https://example.vercel.sh"]'
+      ).each((_, el) => {
+        const crossOrigin = $(el).attr('crossorigin')
+        expect(crossOrigin).toBe('use-credentials')
+      })
+
+      // Inline <script /> (including RSC payload) and <link /> should not have crossorigin attribute
+      $('script:not([src]), link:not([href])').each((_, el) => {
+        const crossOrigin = $(el).attr('crossorigin')
+        expect(crossOrigin).toBeUndefined()
+      })
+
+      // Same origin <script /> and <link /> should not have crossorigin attribute either
+      $('script[src^="/"], link[href^="/"]').each((_, el) => {
+        const crossOrigin = $(el).attr('crossorigin')
+        expect(crossOrigin).toBeUndefined()
+      })
+    })
+  }
+)
diff --git a/test/e2e/app-dir/app-config-crossorigin/next.config.js b/test/e2e/app-dir/app-config-crossorigin/next.config.js
new file mode 100644
index 0000000000000..6e9edd22337ce
--- /dev/null
+++ b/test/e2e/app-dir/app-config-crossorigin/next.config.js
@@ -0,0 +1,16 @@
+module.exports = {
+  /**
+   * The "assetPrefix" here doesn't needs to be real as we doesn't load the page in the browser in this test,
+   * we only care about if all assets prefixed with the "assetPrefix" are having correct "crossOrigin".
+   */
+  assetPrefix: 'https://example.vercel.sh',
+
+  /**
+   * According to HTML5 Spec (https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attributes),
+   * crossorigin="" and crossorigin="anonymous" has the same effect. And ReactDOM's preload methods (preload, preconnect, etc.)
+   * will prefer crossorigin="" to save bytes.
+   *
+   * So we use "use-credentials" here for easier testing.
+   */
+  crossOrigin: 'use-credentials',
+}