Skip to content

Commit

Permalink
Merge branch 'canary' into shu/ku9n
Browse files Browse the repository at this point in the history
  • Loading branch information
shuding committed Feb 14, 2024
2 parents 98a7517 + c48a0f8 commit 0167554
Show file tree
Hide file tree
Showing 17 changed files with 130 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,11 @@ Self-hosting videos may be preferable for several reasons:

### Using Vercel Blob for video hosting

[Vercel Blob](https://vercel.com/docs/storage/vercel-blob) offers an efficient way to host videos, providing a scalable cloud storage solution that works well with Next.js. Here's how you can host a video using Vercel Blob:
[Vercel Blob](https://vercel.com/docs/storage/vercel-blob?utm_source=next-site&utm_medium=docs&utm_campaign=next-website) offers an efficient way to host videos, providing a scalable cloud storage solution that works well with Next.js. Here's how you can host a video using Vercel Blob:

**1. Uploading a video to Vercel Blob**

In your Vercel dashboard, navigate to the "Storage" tab and select your Vercel Blob database. In the Blob table's upper-right corner, find and click the "Upload" button. Then, choose the video file you wish to upload. After the upload completes, the video file will appear in the Blob table.
In your Vercel dashboard, navigate to the "Storage" tab and select your [Vercel Blob](https://vercel.com/docs/storage/vercel-blob?utm_source=next-site&utm_medium=docs&utm_campaign=next-website) store. In the Blob table's upper-right corner, find and click the "Upload" button. Then, choose the video file you wish to upload. After the upload completes, the video file will appear in the Blob table.

Alternatively, you can upload your video using a server action. For detailed instructions, refer to the Vercel documentation on [server-side uploads](https://vercel.com/docs/storage/vercel-blob/server-upload). Vercel also supports [client-side uploads](https://vercel.com/docs/storage/vercel-blob/client-upload). This method may be preferable for certain use cases.

Expand Down Expand Up @@ -212,7 +212,7 @@ In this approach, the page uses the video's `@vercel/blob` URL to display the vi

### Adding subtitles to your video

If you have subtitles for your video, you can easily add them using the `<track>` element inside your `<video>` tag. You can fetch the subtitle file from Vercel Blob in a similar way as the video file. Here's how you can update the `<VideoComponent>` to include subtitles.
If you have subtitles for your video, you can easily add them using the `<track>` element inside your `<video>` tag. You can fetch the subtitle file from [Vercel Blob](https://vercel.com/docs/storage/vercel-blob?utm_source=next-site&utm_medium=docs&utm_campaign=next-website) in a similar way as the video file. Here's how you can update the `<VideoComponent>` to include subtitles.

```jsx filename="app/page.jsx"
async function VideoComponent({ fileName }) {
Expand Down Expand Up @@ -252,7 +252,7 @@ Explore these video streaming platforms for integrating video into your Next.js

### Open source `next-video` component

- Provides a `<Video>` component for Next.js, compatible with various hosting services including Vercel Blob, S3, Backblaze, and Mux.
- Provides a `<Video>` component for Next.js, compatible with various hosting services including [Vercel Blob](https://vercel.com/docs/storage/vercel-blob?utm_source=next-site&utm_medium=docs&utm_campaign=next-website), S3, Backblaze, and Mux.
- [Detailed documentation](https://next-video.dev/docs) for using `next-video.dev` with different hosting services.

### Cloudinary Integration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ For static metadata files, such as `robots.txt`, `favicon.ico`, etc, you should
> Good to know:
>
> - The directory must be named `public`. The name cannot be changed and it's the only directory used to serve static assets.
> - Only assets that are in the `public` directory at [build time](/docs/app/api-reference/next-cli#build) will be served by Next.js. Files added at request time won't be available. We recommend using a third-party service like [AWS S3](https://aws.amazon.com/s3/) for persistent file storage.
> - Only assets that are in the `public` directory at [build time](/docs/app/api-reference/next-cli#build) will be served by Next.js. Files added at request time won't be available. We recommend using a third-party service like [Vercel Blob](https://vercel.com/docs/storage/vercel-blob?utm_source=next-site&utm_medium=docs&utm_campaign=next-website) for persistent file storage.
9 changes: 8 additions & 1 deletion examples/blog-with-comment/hooks/useComment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import React, { useState } from "react";
import useSWR from "swr";
import { useAuth0 } from "@auth0/auth0-react";

const fetcher = (url) => fetch(url).then((res) => res.json());
const fetcher = (url) =>
fetch(url).then((res) => {
if (res.ok) {
return res.json();
}

throw new Error(`${res.status} ${res.statusText} while fetching: ${url}`);
});

export default function useComments() {
const { getAccessTokenSilently } = useAuth0();
Expand Down
123 changes: 83 additions & 40 deletions packages/next/src/build/handle-externals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export function makeExternalHandler({
}) {
let resolvedExternalPackageDirs: Map<string, string>
const looseEsmExternals = config.experimental?.esmExternals === 'loose'
const optOutBundlingPackagesSet = new Set(optOutBundlingPackages)

return async function handleExternals(
context: string,
Expand Down Expand Up @@ -220,27 +221,6 @@ export function makeExternalHandler({
// Also disable esm request when appDir is enabled
const isEsmRequested = dependencyType === 'esm'

/**
* @param localRes the full path to the file
* @returns the externalized path
* @description returns an externalized path if the file is a Next.js file and ends with either `.shared-runtime.js` or `.external.js`
* This is used to ensure that files used across the rendering runtime(s) and the user code are one and the same. The logic in this function
* will rewrite the require to the correct bundle location depending on the layer at which the file is being used.
*/
const resolveNextExternal = (localRes: string) => {
const isExternal = externalPattern.test(localRes)

// if the file ends with .external, we need to make it a commonjs require in all cases
// this is used mainly to share the async local storage across the routing, rendering and user layers.
if (isExternal) {
// it's important we return the path that starts with `next/dist/` here instead of the absolute path
// otherwise NFT will get tripped up
return `commonjs ${normalizePathSep(
localRes.replace(/.*?next[/\\]dist/, 'next/dist')
)}`
}
}

// Don't bundle @vercel/og nodejs bundle for nodejs runtime.
// TODO-APP: bundle route.js with different layer that externals common node_module deps.
// Make sure @vercel/og is loaded as ESM for Node.js runtime
Expand Down Expand Up @@ -288,11 +268,17 @@ export function makeExternalHandler({
// Early return if the request needs to be bundled, such as in the client layer.
// Treat react packages and next internals as external for SSR layer,
// also map react to builtin ones with require-hook.
// Otherwise keep continue the process to resolve the externals.
if (layer === WEBPACK_LAYERS.serverSideRendering) {
const isRelative = request.startsWith('.')
const fullRequest = isRelative
? normalizePathSep(path.join(context, request))
: request

// Check if it's opt out bundling package first
if (optOutBundlingPackagesSet.has(fullRequest)) {
return fullRequest
}
return resolveNextExternal(fullRequest)
}

Expand Down Expand Up @@ -374,28 +360,85 @@ export function makeExternalHandler({
}
}

const shouldBeBundled =
isResourceInPackages(
res,
config.transpilePackages,
resolvedExternalPackageDirs
) ||
(isEsm && isAppLayer) ||
(!isAppLayer && config.experimental.bundlePagesExternals)

if (nodeModulesRegex.test(res)) {
if (isWebpackServerLayer(layer)) {
if (!optOutBundlingPackageRegex.test(res)) {
return // Bundle for server layer
}
return `${externalType} ${request}` // Externalize if opted out
}
const resolvedBundlingOptOutRes = resolveBundlingOptOutPackages({
resolvedRes: res,
optOutBundlingPackageRegex,
config,
resolvedExternalPackageDirs,
isEsm,
isAppLayer,
layer,
externalType,
request,
})
if (resolvedBundlingOptOutRes) {
return resolvedBundlingOptOutRes
}

// if here, we default to bundling the file
return
}
}

if (!shouldBeBundled || optOutBundlingPackageRegex.test(res)) {
return `${externalType} ${request}` // Externalize if not bundled or opted out
function resolveBundlingOptOutPackages({
resolvedRes,
optOutBundlingPackageRegex,
config,
resolvedExternalPackageDirs,
isEsm,
isAppLayer,
layer,
externalType,
request,
}: {
resolvedRes: string
optOutBundlingPackageRegex: RegExp
config: NextConfigComplete
resolvedExternalPackageDirs: Map<string, string>
isEsm: boolean
isAppLayer: boolean
layer: WebpackLayerName | null
externalType: string
request: string
}) {
const shouldBeBundled =
isResourceInPackages(
resolvedRes,
config.transpilePackages,
resolvedExternalPackageDirs
) ||
(isEsm && isAppLayer) ||
(!isAppLayer && config.experimental.bundlePagesExternals)

if (nodeModulesRegex.test(resolvedRes)) {
const isOptOutBundling = optOutBundlingPackageRegex.test(resolvedRes)
if (isWebpackServerLayer(layer)) {
if (isOptOutBundling) {
return `${externalType} ${request}` // Externalize if opted out
}
} else if (!shouldBeBundled || isOptOutBundling) {
return `${externalType} ${request}` // Externalize if not bundled or opted out
}
}
}

// if here, we default to bundling the file
/**
* @param localRes the full path to the file
* @returns the externalized path
* @description returns an externalized path if the file is a Next.js file and ends with either `.shared-runtime.js` or `.external.js`
* This is used to ensure that files used across the rendering runtime(s) and the user code are one and the same. The logic in this function
* will rewrite the require to the correct bundle location depending on the layer at which the file is being used.
*/
function resolveNextExternal(localRes: string) {
const isExternal = externalPattern.test(localRes)

// if the file ends with .external, we need to make it a commonjs require in all cases
// this is used mainly to share the async local storage across the routing, rendering and user layers.
if (isExternal) {
// it's important we return the path that starts with `next/dist/` here instead of the absolute path
// otherwise NFT will get tripped up
return `commonjs ${normalizePathSep(
localRes.replace(/.*?next[/\\]dist/, 'next/dist')
)}`
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export function getResolveRoutes(
// TODO: inherit this from higher up
const protocol =
(req?.socket as TLSSocket)?.encrypted ||
req.headers['x-forwarded-proto'] === 'https'
req.headers['x-forwarded-proto']?.includes('https')
? 'https'
: 'http'

Expand Down
4 changes: 3 additions & 1 deletion packages/next/src/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1785,7 +1785,9 @@ export default class NextNodeServer extends BaseServer {
isUpgradeReq?: boolean
) {
// Injected in base-server.ts
const protocol = req.headers['x-forwarded-proto'] as 'https' | 'http'
const protocol = req.headers['x-forwarded-proto']?.includes('https')
? 'https'
: 'http'

// When there are hostname and port we build an absolute URL
const initUrl =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client'

import { dir } from 'external-package'

export default function Page() {
return <div id="directory-ssr">{dir}</div>
}
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import path from 'path'
import { createNextDescribe } from 'e2e-utils'

createNextDescribe(
'externals-app',
'app-dir - server components externals',
{
files: __dirname,
},
({ next }) => {
({ next, isTurbopack }) => {
it('should have externals for those in config.experimental.serverComponentsExternalPackages', async () => {
const $ = await next.render$('/')

Expand All @@ -22,5 +22,14 @@ createNextDescribe(
const text = $('#directory').text()
expect(text).toBe(path.join(next.testDir, 'node_modules', 'sqlite3'))
})

// Inspect webpack server bundles
if (!isTurbopack) {
it('should externalize serverComponentsExternalPackages for server rendering layer', async () => {
await next.fetch('/client')
const ssrBundle = await next.readFile('.next/server/app/client/page.js')
expect(ssrBundle).not.toContain('external-package-mark')
})
}
}
)
File renamed without changes.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions test/e2e/app-dir/x-forwarded-headers/x-forwarded-headers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,16 @@ createNextDescribe('x-forwarded-headers', { files: __dirname }, ({ next }) => {
expect(headers['middleware-x-forwarded-port']).toBe(reqHeaders.port)
expect(headers['middleware-x-forwarded-proto']).toBe(reqHeaders.proto)
})

it('should work with multiple x-forwarded-* headers', async () => {
const res = await next.fetch('/', {
headers: { 'x-forwarded-proto': 'https, https' },
})

expect(res.status).toBe(200)

const headers = await res.json()
expect(headers['x-forwarded-proto']).toBe('https, https')
})
})
})

0 comments on commit 0167554

Please sign in to comment.