Skip to content

Commit

Permalink
Merge branch 'canary' into 03-07-Enable_minification_for_Turbopack
Browse files Browse the repository at this point in the history
  • Loading branch information
sokra committed Mar 8, 2024
2 parents d0b70fc + ff1d4cb commit e7480ac
Show file tree
Hide file tree
Showing 83 changed files with 719 additions and 962 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -377,11 +377,11 @@ See the [`<Link>` API reference](/docs/app/api-reference/components/link) for mo
### 3. Caching

Next.js has an **in-memory client-side cache** called the [Router Cache](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#caching-data#router-cache). As users navigate around the app, the React Server Component Payload of [prefetched](#2-prefetching) route segments and visited routes are stored in the cache.
Next.js has an **in-memory client-side cache** called the [Router Cache](/docs/app/building-your-application/caching#router-cache). As users navigate around the app, the React Server Component Payload of [prefetched](#2-prefetching) route segments and visited routes are stored in the cache.

This means on navigation, the cache is reused as much as possible, instead of making a new request to the server - improving performance by reducing the number of requests and data transferred.

Learn more about how the [Router Cache](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#caching-data) works and how to configure it.
Learn more about how the [Router Cache](/docs/app/building-your-application/caching#router-cache) works and how to configure it.

### 4. Partial Rendering

Expand All @@ -405,7 +405,7 @@ Browsers perform a "hard navigation" when navigating between pages. The Next.js

### 6. Back and Forward Navigation

By default, Next.js will maintain the scroll position for backwards and forwards navigation, and re-use route segments in the [Router Cache](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#caching-data).
By default, Next.js will maintain the scroll position for backwards and forwards navigation, and re-use route segments in the [Router Cache](/docs/app/building-your-application/caching#router-cache).

### 7. Routing between `pages/` and `app/`

Expand Down
2 changes: 1 addition & 1 deletion examples/blog-starter/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Container from "@/app/_components/container";
import { HeroPost } from "@/app/_components/hero-post";
import { Intro } from "@/app/_components/intro";
import { MoreStories } from "@/app/_components/more-stories";
import { getAllPosts } from "../lib/api";
import { getAllPosts } from "@/lib/api";

export default function Index() {
const allPosts = getAllPosts();
Expand Down
16 changes: 8 additions & 8 deletions examples/blog-starter/src/app/posts/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Metadata } from "next";
import { notFound } from "next/navigation";
import { getAllPosts, getPostBySlug } from "../../../lib/api";
import { CMS_NAME } from "../../../lib/constants";
import markdownToHtml from "../../../lib/markdownToHtml";
import Alert from "../../_components/alert";
import Container from "../../_components/container";
import Header from "../../_components/header";
import { PostBody } from "../../_components/post-body";
import { PostHeader } from "../../_components/post-header";
import { getAllPosts, getPostBySlug } from "@/lib/api";
import { CMS_NAME } from "@/lib/constants";
import markdownToHtml from "@/lib/markdownToHtml";
import Alert from "@/app/_components/alert";
import Container from "@/app/_components/container";
import Header from "@/app/_components/header";
import { PostBody } from "@/app/_components/post-body";
import { PostHeader } from "@/app/_components/post-header";

export default async function Post({ params }: Params) {
const post = getPostBySlug(params.slug);
Expand Down
50 changes: 29 additions & 21 deletions examples/with-mux-video/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

This example uses Mux Video, an API-first platform for video. The example features video uploading and playback in a Next.js application.

## Demo
## Try it out

### [https://with-mux-video.vercel.app/](https://with-mux-video.vercel.app/)

### This project was used to create [stream.new](https://stream.new/)
- [https://with-mux-video.vercel.app/](https://with-mux-video.vercel.app/)
- This project was used to create [stream.new](https://stream.new/)

## Deploy your own

Expand All @@ -16,6 +15,8 @@ Deploy the example using [Vercel](https://vercel.com/home):

## How to use

### Step 1. Create a Next app with this example

Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:

```bash
Expand All @@ -30,38 +31,32 @@ yarn create next-app --example with-mux-video with-mux-video-app
pnpm create next-app --example with-mux-video with-mux-video-app
```

## Note

**Important:** When creating uploads, this demo sets `cors_origin: "*"` in the [`pages/api/upload.js`](pages/api/upload.js) file. For extra security, you should update this value to be something like `cors_origin: 'https://your-app.com'`, to restrict uploads to only be allowed from your application.

This example uses:

- [SWR](https://swr.vercel.app/) — dynamically changing the `refreshInterval` depending on if the client should be polling for updates or not
- [`/pages/api`](pages/api) routes — a couple endpoints for making authenticated requests to the Mux API.
- Dynamic routes using [`getStaticPaths` and `fallback: true`](https://nextjs.org/docs/basic-features/data-fetching/get-static-paths), as well as dynamic API routes.

## Configuration
```bash
bunx create-next-app --example with-mux-video with-mux-video-app
```

### Step 1. Create an account in Mux
### Step 2. Create an account in Mux

All you need to set this up is a [Mux account](https://mux.com). You can sign up for free and pricing is pay-as-you-go. There are no upfront charges, you get billed monthly only for what you use.
All you need to run this example is a [Mux account](https://www.mux.com?utm_source=create-next-app&utm_medium=with-mux-video&utm_campaign=create-next-app). You can sign up for free. There are no upfront charges -- you get billed monthly only for what you use.

Without entering a credit card on your Mux account all videos are in “test mode” which means they are watermarked and clipped to 10 seconds. If you enter a credit card all limitations are lifted and you get \$20 of free credit. The free credit should be plenty for you to test out and play around with everything before you are charged.
Without entering a credit card on your Mux account all videos are in “test mode” which means they are watermarked and clipped to 10 seconds. If you enter a credit card all limitations are lifted and you get \$20 of free credit. The free credit should be plenty for you to test out and play around with everything.

### Step 2. Set up environment variables
### Step 3. Set up environment variables

Copy the `.env.local.example` file in this directory to `.env.local` (which will be ignored by Git):

```bash
cp .env.local.example .env.local
```

Then, go to the [settings page](https://dashboard.mux.com/settings/access-tokens) in your Mux dashboard set each variable on `.env.local`, get a new **API Access Token** and set each variable in `.env.local`:
Then, go to the [settings page](https://dashboard.mux.com/settings/access-tokens) in your Mux dashboard, get a new **API Access Token**. Use that token to set the variables in `.env.local`:

- `MUX_TOKEN_ID` should be the `TOKEN ID` of your new token
- `MUX_TOKEN_SECRET` should be `TOKEN SECRET`

### Step 3. Deploy on Vercel
At this point, you're good to `npm run dev` or `yarn dev` or `pnpm dev`. However, if you want to deploy, read on:

### Step 4. Deploy on Vercel

You can deploy this app to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).

Expand All @@ -75,3 +70,16 @@ vercel secrets add next_example_mux_token_secret <MUX_TOKEN_SECRET>
```

Then push the project to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) to deploy.

## Notes

### Preparing for Production

**Important:** When creating uploads, this demo sets `cors_origin: "*"` in the [`app/(upload)/page.tsx`](<app/(upload)/page.tsx>) file. For extra security, you should update this value to be something like `cors_origin: 'https://your-app.com'`, to restrict uploads to only be allowed from your application.

### How it works

1. Users land on the home page, `app/(upload)/page.tsx`. The Mux [Direct Uploads API](https://docs.mux.com/api-reference#video/tag/direct-uploads?utm_source=create-next-app&utm_medium=with-mux-video&utm_campaign=create-next-app) provides an endpoint to [Mux Uploader React](https://docs.mux.com/guides/mux-uploader?utm_source=create-next-app&utm_medium=with-mux-video&utm_campaign=create-next-app).
1. The user uploads a video with Mux Uploader. When their upload is complete, Mux Uploader calls a [server action](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) that redirects to...
1. `app/(upload)/asset/[assetId]/page.tsx`, which polls the [Asset API](https://docs.mux.com/api-reference#video/tag/assets?utm_source=create-next-app&utm_medium=with-mux-video&utm_campaign=create-next-app) via server action, waiting for the asset to be ready. Once the asset is ready, it redirects to...
1. `app/v/[assetId]/page.tsx`, where users can watch their video using [Mux Player React](https://docs.mux.com/guides/mux-player-web?utm_source=create-next-app&utm_medium=with-mux-video&utm_campaign=create-next-app). This page uses the [Mux Image API](https://docs.mux.com/guides/get-images-from-a-video) and the [Next.js Metadata API](https://nextjs.org/docs/app/building-your-application/optimizing/metadata) to provide an og images tailored to each video.
13 changes: 13 additions & 0 deletions examples/with-mux-video/app/(upload)/MuxUploader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use client";

import { type ComponentPropsWithoutRef } from "react";

import LibMuxUploader from "@mux/mux-uploader-react";

type Props = {
endpoint: ComponentPropsWithoutRef<typeof LibMuxUploader>["endpoint"];
onSuccess: () => void;
};
export default function MuxUploader({ endpoint, onSuccess }: Props) {
return <LibMuxUploader endpoint={endpoint} onSuccess={() => onSuccess()} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"use client";

import { useEffect, useState } from "react";

import { Status } from "./types";
import Link from "@/app/_components/Link";

const Oops = () => (
<p>
This is awkward. Let's <Link href="/">refresh</Link> and try again.
</p>
);

type Props = {
initialStatus: Status;
checkAssetStatus: () => Promise<Status>;
};
export default function AssetStatusPoll({
initialStatus,
checkAssetStatus,
}: Props) {
const [{ status, errors }, setStatus] = useState<Status>(() => initialStatus);

useEffect(() => {
const poll = async () => setStatus(await checkAssetStatus());
const interval = setInterval(poll, 1000);
return () => clearInterval(interval);
}, [checkAssetStatus]);

switch (status) {
case "preparing":
return <p className="animate-pulse">Asset is preparing...</p>;
case "errored":
return (
<div>
<p className="mb-4">Asset encountered an error.</p>
{Array.isArray(errors) && (
<ul className="mb-4">
{errors.map((error, key) => (
<li key={key}>{JSON.stringify(error)}</li>
))}
</ul>
)}
<Oops />
</div>
);
case "ready":
return (
<div>
<p className="mb-4">
Asset is ready. The app really should've redirected you to it by
now.
</p>
<Oops />
</div>
);
default:
return (
<div>
<p className="mb-4">Asset is in an unknown state.</p>
<pre className="mb-4">{JSON.stringify({ status, errors })}</pre>
<Oops />
</div>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Layout({ children }: { children: React.ReactNode }) {
return <div className="font-mono text-sm">{children}</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Loading() {
return <div>Checking asset status...</div>;
}
51 changes: 51 additions & 0 deletions examples/with-mux-video/app/(upload)/asset/[assetId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Mux from "@mux/mux-node";
import { redirect } from "next/navigation";
import { Status } from "./types";
import AssetStatusPoll from "./AssetStatusPoll";

// reads MUX_TOKEN_ID and MUX_TOKEN_SECRET from your environment
const mux = new Mux();

const checkAssetStatus = async (assetId: string): Promise<Status> => {
const asset = await mux.video.assets.retrieve(assetId);

// if the asset is ready and it has a public playback ID,
// (which it should, considering the upload settings we used)
// redirect to its playback page
if (asset.status === "ready") {
const playbackIds = asset.playback_ids;
if (Array.isArray(playbackIds)) {
const playbackId = playbackIds.find((id) => id.policy === "public");
if (playbackId) {
redirect(`/v/${playbackId.id}`);
}
}
}

return {
status: asset.status,
errors: asset.errors,
};
};

// For better performance, we could cache and use a Mux webhook to invalidate the cache.
// https://docs.mux.com/guides/listen-for-webhooks
// For this example, calling the Mux API on each request and then polling is sufficient.
export const dynamic = "force-dynamic";

export default async function Page({
params: { assetId },
}: {
params: { assetId: string };
}) {
const initialStatus = await checkAssetStatus(assetId);
return (
<AssetStatusPoll
initialStatus={initialStatus}
checkAssetStatus={async () => {
"use server";
return await checkAssetStatus(assetId);
}}
/>
);
}
6 changes: 6 additions & 0 deletions examples/with-mux-video/app/(upload)/asset/[assetId]/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type Mux from "@mux/mux-node";

export type Status = {
status: Mux.Video.Assets.Asset["status"];
errors: Mux.Video.Assets.Asset["errors"];
};
57 changes: 57 additions & 0 deletions examples/with-mux-video/app/(upload)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Link from "../_components/Link";
import { MUX_HOME_PAGE_URL } from "../constants";

export default function Layout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<>
<header className="mb-8">
<h1 className="font-bold text-4xl lg:text-5xl mb-2">
Welcome to Mux + Next.js
</h1>
<p className="italic">Get started by uploading a video</p>
</header>
<p className="mb-4">
<Link
href={MUX_HOME_PAGE_URL}
target="_blank"
rel="noopener noreferrer"
>
Mux
</Link>{" "}
provides APIs for developers working with video.
<br />
This example is useful if you want to build:
</p>
<ul className="list-disc pl-8 mb-4">
<li>A video on demand service like Youtube or Netflix</li>
<li>
A platform that supports user uploaded videos like TikTok or Instagram
</li>
<li>Video into your custom CMS</li>
</ul>
<p className="mb-4">
Uploading a video uses the Mux{" "}
<Link href="https://docs.mux.com/docs/direct-upload">
direct upload API
</Link>
. When the upload is complete your video will be processed by Mux and
available for playback on a sharable URL.
</p>
<p>
To learn more,{" "}
<Link
href="https://github.com/vercel/next.js/tree/canary/examples/with-mux-video"
target="_blank"
rel="noopener noreferrer"
>
check out the source code on GitHub
</Link>
.
</p>
<hr className="my-8 bg-gray-500" />
{children}
</>
);
}
3 changes: 3 additions & 0 deletions examples/with-mux-video/app/(upload)/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Loading() {
return <div>Preparing upload...</div>;
}

0 comments on commit e7480ac

Please sign in to comment.