Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ESLint Rule to Prevent Redirect in Try-Catch Block #68108

Open
wants to merge 8 commits into
base: canary
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ Next.js provides an ESLint plugin, [`eslint-plugin-next`](https://www.npmjs.com/
| <Check size={18} /> | [@next/next/no-title-in-document-head](/docs/messages/no-title-in-document-head) | Prevent usage of `<title>` with `Head` component from `next/document`. |
| <Check size={18} /> | @next/next/no-typos | Prevent common typos in [Next.js's data fetching functions](/docs/pages/building-your-application/data-fetching) |
| <Check size={18} /> | [@next/next/no-unwanted-polyfillio](/docs/messages/no-unwanted-polyfillio) | Prevent duplicate polyfills from Polyfill.io. |
| <Check size={18} /> | [@next/next/no-redirect-in-try-catch](/docs/messages/no-redirect-in-try-catch) | Prevent usage of `redirect` in try-catch block. |

If you already have ESLint configured in your application, we recommend extending from this plugin directly instead of including `eslint-config-next` unless a few conditions are met. Refer to the [Recommended Plugin Ruleset](#recommended-plugin-ruleset) to learn more.

Expand Down
43 changes: 43 additions & 0 deletions errors/no-redirect-in-try-catch.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
title: No Redirect in Try-Catch
---

> Prevent usage of `redirect` in try-catch block.

## Why This Error Occurred

You attempted to use the `redirect` function within a try-catch block. When `redirect` is called, it throws a `NEXT_REDIRECT` error internally. If this error is caught by the catch block, it prevents the redirect from executing as intended.

## Possible Ways to Fix It

Move the `redirect` call outside of the try-catch block. This ensures that the `NEXT_REDIRECT` error is not caught and the redirect can proceed without interference.

## Example

### Incorrect Usage:

```javascript
try {
// some code that might throw an error
redirect('/some-path')
} catch (error) {
// handle error
}
```

### Correct Usage:

```javascript
try {
// some code that might throw an error
} catch (error) {
// handle error
}
// Redirect outside of the try-catch block
redirect('/some-path')
```

## Useful Links

- [Next.js Redirect Documentation](/docs/app/building-your-application/routing/redirecting#redirect-function)
- [Next.js Error Handling](/docs/app/building-your-application/routing/error-handling)
2 changes: 2 additions & 0 deletions packages/eslint-plugin-next/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = {
'no-title-in-document-head': require('./rules/no-title-in-document-head'),
'no-typos': require('./rules/no-typos'),
'no-unwanted-polyfillio': require('./rules/no-unwanted-polyfillio'),
'no-redirect-in-try-catch': require('./rules/no-redirect-in-try-catch'),
},
configs: {
recommended: {
Expand Down Expand Up @@ -49,6 +50,7 @@ module.exports = {
'@next/next/no-duplicate-head': 'error',
'@next/next/no-head-import-in-document': 'error',
'@next/next/no-script-component-in-head': 'error',
'@next/next/no-redirect-in-try-catch': 'error',
},
},
'core-web-vitals': {
Expand Down
49 changes: 49 additions & 0 deletions packages/eslint-plugin-next/src/rules/no-redirect-in-try-catch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { defineRule } from '../utils/define-rule'
const url = 'https://nextjs.org/docs/messages/no-redirect-in-try-catch'

export = defineRule({
meta: {
docs: {
description: 'Prevent usage of `redirect` in try-catch block.',
recommended: true,
url,
},
type: 'problem',
schema: [],
},
create(context) {
function checkForRedirectCall(statement) {
if (
statement.type === 'ExpressionStatement' &&
statement.expression.type === 'CallExpression' &&
statement.expression.callee.name === 'redirect'
) {
context.report({
node: statement,
message: `Do not use \`redirect\` within a try-catch block. Move the \`redirect\` call outside of the try-catch block. See: ${url}`,
})
} else if (statement.type === 'BlockStatement') {
statement.body.forEach((innerStatement) => {
checkForRedirectCall(innerStatement)
})
} else if (statement.type === 'IfStatement') {
checkForRedirectCall(statement.consequent)
if (statement.alternate) {
checkForRedirectCall(statement.alternate)
}
}
}

return {
ImportDeclaration(node) {
if (node.source.value !== 'next/navigation') {
return
}
},
TryStatement(node) {
const tryBlockStatements = node.block.body
tryBlockStatements.forEach(checkForRedirectCall)
},
}
},
})
71 changes: 71 additions & 0 deletions test/unit/eslint-plugin-next/no-redirect-in-try-catch.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import rule from '@next/eslint-plugin-next/dist/rules/no-redirect-in-try-catch'
import { RuleTester } from 'eslint'
;(RuleTester as any).setDefaultConfig({
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
modules: true,
jsx: true,
},
},
})
const ruleTester = new RuleTester()

ruleTester.run('no-redirect-in-try-catch', rule, {
valid: [
`'use server'

import { redirect } from "next/navigation"

export async function navigate(data) {
redirect(\`/posts/\${data.get('id')}\`)
}`,
],
invalid: [
{
code: `
'use server'

import { redirect } from "next/navigation"

export async function navigate(data) {
try {
redirect(\`/posts/\${data.get('id')}\`)
} catch (e) {
console.error(e);
}
}`,
filename: 'app/actions.ts',
errors: [
{
message:
'Do not use `redirect` within a try-catch block. Move the `redirect` call outside of the try-catch block. See: https://nextjs.org/docs/messages/no-redirect-in-try-catch',
},
],
},
{
code: `
'use server'

import { redirect } from "next/navigation"

export async function navigate(data) {
try {
if (data.id) {
redirect(\`/posts/\${data.id}\`)
}
} catch (e) {
console.error(e);
}
}`,
filename: 'app/actions.ts',
errors: [
{
message:
'Do not use `redirect` within a try-catch block. Move the `redirect` call outside of the try-catch block. See: https://nextjs.org/docs/messages/no-redirect-in-try-catch',
},
],
},
],
})
Loading