diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 85feefc08f..8a3d0b82ac 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM node:lts@sha256:35a5dd72bcac4bce43266408b58a02be6ff0b6098ffa6f5435aeea980a8951d7 +FROM node:lts@sha256:0e910f435308c36ea60b4cfd7b80208044d77a074d16b768a81901ce938a62dc RUN apt-get update && \ apt-get install -fy libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdbus-1-3 libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 && \ diff --git a/.github/assets/bluesky.svg b/.github/assets/bluesky.svg new file mode 100644 index 0000000000..d6a6d9d0d9 --- /dev/null +++ b/.github/assets/bluesky.svg @@ -0,0 +1 @@ + diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml deleted file mode 100644 index 1ab482ad65..0000000000 --- a/.github/codeql/codeql-config.yml +++ /dev/null @@ -1,10 +0,0 @@ -paths: - - 'packages/*/dist/**' - - 'packages/nuxt/bin/**' - - 'packages/schema/schema/**' -paths-ignore: - - 'test/**' - - '**/*.test.js' - - '**/*.test.ts' - - '**/*.test.tsx' - - '**/__tests__/**' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5fe6772524..6fc0803c60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: run: pnpm test:attw - name: Cache dist - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: retention-days: 3 name: dist @@ -69,6 +69,9 @@ jobs: codeql: runs-on: ubuntu-latest timeout-minutes: 10 + strategy: + matrix: + language: ['javascript-typescript', 'actions'] permissions: actions: read contents: read @@ -78,7 +81,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Initialize CodeQL - uses: github/codeql-action/init@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 with: config: | paths: @@ -90,13 +93,14 @@ jobs: - '**/*.spec.ts' - '**/*.test.ts' - '**/__snapshots__/**' - languages: javascript-typescript - queries: +security-and-quality + # codeql bug: #L20C9:9: A parse error occurred: `Unexpected token`. + - 'packages/vite/src/runtime/vite-node.mjs' + languages: ${{ matrix.language }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 with: - category: "/language:javascript-typescript" + category: "/language:${{ matrix.language }}" typecheck: runs-on: ${{ matrix.os }} @@ -251,7 +255,7 @@ jobs: TEST_PAYLOAD: ${{ matrix.payload }} SKIP_BUNDLE_SIZE: ${{ github.event_name != 'push' || matrix.env == 'dev' || matrix.builder == 'webpack' || matrix.context == 'default' || matrix.payload == 'js' || runner.os == 'Windows' }} - - uses: codecov/codecov-action@7f8b4b4bde536c465e797be725718b88c5d95e0e # v5.1.1 + - uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2 if: github.event_name != 'push' && matrix.env == 'built' && matrix.builder == 'vite' && matrix.context == 'default' && matrix.os == 'ubuntu-latest' && matrix.manifest == 'manifest-on' with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/docs-check-links.yml b/.github/workflows/docs-check-links.yml index 18aa5cbec0..5f7b8d97a1 100644 --- a/.github/workflows/docs-check-links.yml +++ b/.github/workflows/docs-check-links.yml @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Lychee link checker - uses: lycheeverse/lychee-action@4aa18b6ccdac05029fab067313a6a04f941e6494 # for v1.8.0 + uses: lycheeverse/lychee-action@f796c8b7d468feb9b8c0a46da3fac0af6874d374 # for v1.8.0 with: # arguments with file types to check args: >- diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index e9895a7579..cd4b3fc9af 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 if: github.repository == 'nuxt/nuxt' && success() with: name: SARIF file @@ -68,7 +68,7 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 if: github.repository == 'nuxt/nuxt' && success() with: sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index 815f0415e7..45e3babad0 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,9 @@ coverage # Intellij idea *.iml .idea +!.idea/nuxt.iml +!.idea/modules.xml +!.idea/inspectionProfiles/Project_Default.xml # OSX .DS_Store diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000000..03d9549ea8 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000..e0ae4b3716 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/nuxt.iml b/.idea/nuxt.iml new file mode 100644 index 0000000000..85e8aa1a9d --- /dev/null +++ b/.idea/nuxt.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 5c5e76cc4c..b0834e7dee 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ Follow the docs to [Set Up Your Local Development Environment](https://nuxt.com/ ## 🔗 Follow Us

- Discord  Twitter  GitHub + Discord  Twitter  GitHub  Bluesky

## ⚖️ License diff --git a/docs/1.getting-started/12.upgrade.md b/docs/1.getting-started/12.upgrade.md index 00504c21bc..4f8620f72c 100644 --- a/docs/1.getting-started/12.upgrade.md +++ b/docs/1.getting-started/12.upgrade.md @@ -85,6 +85,9 @@ export default defineNuxtConfig({ // } // } // }, + // features: { + // inlineStyles: true + // }, // unhead: { // renderSSRHeadOptions: { // omitLineBreaks: false @@ -281,6 +284,28 @@ export default defineNuxtConfig({ }) ``` +#### More Granular Inline Styles + +🚦 **Impact Level**: Moderate + +Nuxt will now only inline styles for Vue components, not global CSS. + +##### What Changed + +Previously, Nuxt would inline all CSS, including global styles, and remove `` elements to separate CSS files. Now, Nuxt will only do this for Vue components (which previously produced separate chunks of CSS). We think this is a better balance of reducing separate network requests (just as before, there will not be separate requests for individual `.css` files per-page or per-component on the initial load), as well as allowing caching of a single global CSS file and reducing the document download size of the initial request. + +##### Migration Steps + +This feature is fully configurable and you can revert to the previous behavior by setting `inlineStyles: true` to inline global CSS as well as per-component CSS. + +```ts twoslash [nuxt.config.ts] +export default defineNuxtConfig({ + features: { + inlineStyles: true + } +}) +``` + #### Scan Page Meta After Resolution 🚦 **Impact Level**: Minimal diff --git a/docs/1.getting-started/9.prerendering.md b/docs/1.getting-started/9.prerendering.md index 5644911820..7f72b4bc9c 100644 --- a/docs/1.getting-started/9.prerendering.md +++ b/docs/1.getting-started/9.prerendering.md @@ -159,7 +159,7 @@ prerenderRoutes(["/some/other/url"]); This is called before prerendering for additional routes to be registered. -```ts [nitro.config.ts] +```ts [nuxt.config.ts] export default defineNuxtConfig({ hooks: { async "prerender:routes"(ctx) { @@ -178,7 +178,7 @@ export default defineNuxtConfig({ This is called for each route during prerendering. You can use this for fine grained handling of each route that gets prerendered. -```ts [nitro.config.ts] +```ts [nuxt.config.ts] export default defineNuxtConfig({ nitro: { hooks: { diff --git a/docs/2.guide/1.concepts/1.auto-imports.md b/docs/2.guide/1.concepts/1.auto-imports.md index c650abc685..664c5de7d2 100644 --- a/docs/2.guide/1.concepts/1.auto-imports.md +++ b/docs/2.guide/1.concepts/1.auto-imports.md @@ -143,6 +143,28 @@ export default defineNuxtConfig({ This will disable auto-imports completely but it's still possible to use [explicit imports](#explicit-imports) from `#imports`. +### Partially Disabling Auto-imports + +If you want framework-specific functions like `ref` to remain auto-imported but wish to disable auto-imports for your own code (e.g., custom composables), you can set the `imports.scan` option to `false` in your `nuxt.config.ts` file: + +```ts +export default defineNuxtConfig({ + imports: { + scan: false + } +}) +``` + +With this configuration: +- Framework functions like `ref`, `computed`, or `watch` will still work without needing manual imports. +- Custom code, such as composables, will need to be manually imported in your files. + +::warning +**Caution:** This setup has certain limitations: +- If you structure your project with layers, you will need to explicitly import the composables from each layer, rather than relying on auto-imports. +- This breaks the layer system’s override feature. If you use `imports.scan: false`, ensure you understand this side-effect and adjust your architecture accordingly. +:: + ## Auto-imported Components Nuxt also automatically imports components from your `~/components` directory, although this is configured separately from auto-importing composables and utility functions. diff --git a/docs/2.guide/1.concepts/9.code-style.md b/docs/2.guide/1.concepts/9.code-style.md index edbfa3c490..4451635ca7 100644 --- a/docs/2.guide/1.concepts/9.code-style.md +++ b/docs/2.guide/1.concepts/9.code-style.md @@ -8,9 +8,7 @@ description: "Nuxt supports ESLint out of the box" The recommended approach for Nuxt is to enable ESLint support using the [`@nuxt/eslint`](https://eslint.nuxt.com/packages/module) module, that will setup project-aware ESLint configuration for you. :::callout{icon="i-ph-lightbulb"} -The module is designed for the [new ESLint flat config format](https://eslint.org/docs/latest/use/configure/configuration-files-new) with is the [default format since ESLint v9](https://eslint.org/blog/2024/04/eslint-v9.0.0-released/). - -If you are using the legacy `.eslintrc` config, you will need to [configure manually with `@nuxt/eslint-config`](https://eslint.nuxt.com/packages/config#legacy-config-format). We highly recommend you to migrate over the flat config to be future-proof. +The module is designed for the [new ESLint flat config format](https://eslint.org/docs/latest/use/configure/configuration-files-new) with is the [default format since ESLint v9](https://eslint.org/blog/2024/04/eslint-v9.0.0-released/). If you are using the legacy `.eslintrc` config, you will need to [configure manually with `@nuxt/eslint-config`](https://eslint.nuxt.com/packages/config#legacy-config-format). We highly recommend you to migrate over the flat config to be future-proof. ::: ## Quick Setup diff --git a/docs/2.guide/2.directory-structure/1.pages.md b/docs/2.guide/2.directory-structure/1.pages.md index 57d88ddd5c..1efaacc2aa 100644 --- a/docs/2.guide/2.directory-structure/1.pages.md +++ b/docs/2.guide/2.directory-structure/1.pages.md @@ -266,17 +266,27 @@ console.log(route.meta.title) // My home page If you are using nested routes, the page metadata from all these routes will be merged into a single object. For more on route meta, see the [vue-router docs](https://router.vuejs.org/guide/advanced/meta.html#route-meta-fields). -Much like `defineEmits` or `defineProps` (see [Vue docs](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)), `definePageMeta` is a **compiler macro**. It will be compiled away so you cannot reference it within your component. Instead, the metadata passed to it will be hoisted out of the component. Therefore, the page meta object cannot reference the component (or values defined on the component). However, it can reference imported bindings. +Much like `defineEmits` or `defineProps` (see [Vue docs](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)), `definePageMeta` is a **compiler macro**. It will be compiled away so you cannot reference it within your component. Instead, the metadata passed to it will be hoisted out of the component. +Therefore, the page meta object cannot reference the component. However, it can reference imported bindings, as well as locally defined **pure functions**. + +::warning +Make sure not to reference any reactive data or functions that cause side effects. This can lead to unexpected behavior. +:: ```vue ``` diff --git a/docs/2.guide/3.going-further/1.features.md b/docs/2.guide/3.going-further/1.features.md index 28342b2725..c02f9fb8df 100644 --- a/docs/2.guide/3.going-further/1.features.md +++ b/docs/2.guide/3.going-further/1.features.md @@ -16,7 +16,7 @@ You can also pass a function that receives the path of a Vue component and retur ```ts [nuxt.config.ts] export default defineNuxtConfig({ features: { - inlineStyles: true // or a function to determine inlining + inlineStyles: false // or a function to determine inlining } }) ``` @@ -74,6 +74,9 @@ export default defineNuxtConfig({ } } }, + features: { + inlineStyles: true + }, unhead: { renderSSRHeadOptions: { omitLineBreaks: false diff --git a/docs/2.guide/4.recipes/4.sessions-and-authentication.md b/docs/2.guide/4.recipes/4.sessions-and-authentication.md new file mode 100644 index 0000000000..6a45db6bbb --- /dev/null +++ b/docs/2.guide/4.recipes/4.sessions-and-authentication.md @@ -0,0 +1,203 @@ +--- +title: 'Sessions and Authentication' +description: "Authentication is an extremely common requirement in web apps. This recipe will show you how to implement basic user registration and authentication in your Nuxt app." +--- + +## Introduction + +In this recipe we'll be setting up authentication in a full-stack Nuxt app using [Nuxt Auth Utils](https://github.com/Atinux/nuxt-auth-utils) which provides convenient utilities for managing client-side and server-side session data. + +The module uses secured & sealed cookies to store session data, so you don't need to setup a database to store session data. + +## Install nuxt-auth-utils + +Install the `nuxt-auth-utils` module using the `nuxi` CLI. + +```bash [Terminal] +npx nuxi@latest module add auth-utils +``` + +::callout +This command will install `nuxt-auth-utils` as dependency and push it in the `modules` section of our `nuxt.config.ts` +:: + +## Cookie Encryption Key + +As `nuxt-auth-utils` uses sealed cookies to store session data, session cookies are encrypted using a secret key from the `NUXT_SESSION_PASSWORD` environment variable. + +::note +If not set, this environment variable will be added to your `.env` automatically when running in development mode. +:: + +```dotenv [.env] +NUXT_SESSION_PASSWORD=a-random-password-with-at-least-32-characters +``` + +::important +You'll need to add this environment variable to your production environment before deploying. +:: + +## Login API Route + +For this recipe, we'll create a simple API route to sign-in a user based on static data. + +Let's create a `/api/login` API route that will accept a POST request with the email and password in the request body. + +```ts [server/api/login.post.ts] +import { z } from 'zod' + +const bodySchema = z.object({ + email: z.string().email(), + password: z.string().min(8) +}) + +export default defineEventHandler(async (event) => { + const { email, password } = await readValidatedBody(event, bodySchema.parse) + + if (email === 'admin@admin.com' && password === 'iamtheadmin') { + // set the user session in the cookie + // this server util is auto-imported by the auth-utils module + await setUserSession(event, { + user: { + name: 'John Doe' + } + }) + return {} + } + throw createError({ + statusCode: 401, + message: 'Bad credentials' + }) +}) +``` + +::callout +Make sure to install the `zod` dependency in your project (`npm i zod`). +:: + +::tip{to="https://github.com/atinux/nuxt-auth-utils#server-utils"} +Read more about the `setUserSession` server helper exposed by `nuxt-auth-utils`. +:: + +## Login Page + +The module exposes a Vue composable to know if a user is authenticated in our application: + +```vue + +``` + +Let's create a login page with a form to submit the login data to our `/api/login` route. + +```vue [pages/login.vue] + + + +``` + +## Protect API Routes + +Protecting server routes is key to making sure your data is safe. Client-side middleware is helpful for the user, but without server-side protection your data can still be accessed. It is critical to protect any routes with sensitive data, we should return a 401 error if the user is not logged in on those. + +The `auth-utils` module provides the `requireUserSession` utility function to help make sure that users are logged in and have an active session. + +Let's create an example of a `/api/user/stats` route that only authenticated users can access. + +```ts [server/api/user/stats.get.ts] +export default defineEventHandler(async (event) => { + // make sure the user is logged in + // This will throw a 401 error if the request doesn't come from a valid user session + const { user } = await requireUserSession(event) + + // TODO: Fetch some stats based on the user + + return {} +}); +``` + +## Protect App Routes + +Our data is safe with the server-side route in place, but without doing anything else, unauthenticated users would probably get some odd data when trying to access the `/users` page. We should create a [client-side middleware](https://nuxt.com/docs/guide/directory-structure/middleware) to protect the route on the client side and redirect users to the login page. + +`nuxt-auth-utils` provides a convenient `useUserSession` composable which we'll use to check if the user is logged in, and redirect them if they are not. + +We'll create a middleware in the `/middleware` directory. Unlike on the server, client-side middleware is not automatically applied to all endpoints, and we'll need to specify where we want it applied. + +```typescript [middleware/authenticated.ts] +export default defineNuxtRouteMiddleware(() => { + const { loggedIn } = useUserSession() + + // redirect the user to the login screen if they're not authenticated + if (!loggedIn.value) { + return navigateTo('/login') + } +}) +``` + +## Home Page + +Now that we have our app middleware to protect our routes, we can use it on our home page that display our authenticated user informations. If the user is not authenticated, they will be redirected to the login page. + +We'll use [`definePageMeta`](/docs/api/utils/define-page-meta) to apply the middleware to the route that we want to protect. + +```vue [pages/index.vue] + + + +``` + +We also added a logout button to clear the session and redirect the user to the login page. + +## Conclusion + +We've successfully set up a very basic user authentication and session management in our Nuxt app. We've also protected sensitive routes on the server and client side to ensure that only authenticated users can access them. + +As next steps, you can: +- Add authentication using the [20+ supported OAuth providers](https://github.com/atinux/nuxt-auth-utils?tab=readme-ov-file#supported-oauth-providers) +- Add a database to store users, see [Nitro SQL Database](https://nitro.build/guide/database) or [NuxtHub SQL Database](https://hub.nuxt.com/docs/features/database) +- Let user signup with email & password using [password hashing](https://github.com/atinux/nuxt-auth-utils?tab=readme-ov-file#password-hashing) +- Add support for [WebAuthn / Passkeys](https://github.com/atinux/nuxt-auth-utils?tab=readme-ov-file#webauthn-passkey) + +Checkout the open source [atidone repository](https://github.com/atinux/atidone) for a full example of a Nuxt app with OAuth authentication, database and CRUD operations. diff --git a/docs/3.api/4.commands/add.md b/docs/3.api/4.commands/add.md index 6b88b119fd..4ed7d1eb09 100644 --- a/docs/3.api/4.commands/add.md +++ b/docs/3.api/4.commands/add.md @@ -8,16 +8,30 @@ links: size: xs --- + ```bash [Terminal] -npx nuxi add [--cwd] [--force]