Files
nhost/docs/getting-started/tutorials/nextjs/2-protected-routes.mdx
David Barroso 8c71dd9db9 feat (docs): add tutorials for supported frameworks (#3469)
### **PR Type**
Enhancement


___

### **Description**
- Add cross-framework Todos CRUD examples

- Introduce file upload/download tutorials

- Provide unified AuthProvider context implementations

- Include email templates and backend actions


___



<details> <summary><h3> File Walkthrough</h3></summary>

<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>19
files</summary><table>
<tr>
<td><strong>Todos.tsx</strong><dd><code>Add web demo Todos CRUD
page</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-53f3b3d582fef21d5ec90cb590b73afcf09407071dba60883ed1ed7360955fc5">+648/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>todos.tsx</strong><dd><code>Add React Native tutorial
Todos</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-3f6be5ef4f443091687addd404fe71f219498b9db7dea992d18d78b4f1b6ffa3">+561/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>commonStyles.ts</strong><dd><code>Add common React Native
tutorial styles</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-bf0de16179ecc80a8e88e223c890dc2c73c30b4a9b7cadd62e910ca015ce342b">+667/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>Todos.tsx</strong><dd><code>Add React tutorial Todos
page</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-be700e4847b0a745821f156c381e583097f2083123065a45a20611c2ba1876a7">+504/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>files.tsx</strong><dd><code>Add React Native tutorial file
upload</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-d116805053a943f271a3297d06d14dba39c0f5775080e67e1e9e2778c176e9da">+454/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>Files.tsx</strong><dd><code>Add React tutorial Files
page</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-cdbca4ed68690df84463df7765dff52c85a60502f175c19519c8b42474e9282c">+404/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>signup.tsx</strong><dd><code>Enhance signup flow with email
verification</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-262b09b9dd7234cad96fe092d7131a23451c9e50b98c126c9e36599b3a127ac6">+185/-141</a></td>

</tr>

<tr>
<td><strong>FilesClient.tsx</strong><dd><code>Add Next.js Files client
component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-ea74386f232b9ae7e7957ab4eb1f0d1d6076b338173e8b1e917369fb7f1b39bb">+359/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>TodosClient.tsx</strong><dd><code>Add Next.js Todos client
component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-96efb4db6bd61a7faee3c383e77b092eccee1a1876770c36691e7356268cfad4">+368/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>AuthProvider.tsx</strong><dd><code>Implement React
AuthProvider context</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-4cc7e41420d71d448eeec4f77043e0c5bff2c606986439454dade5ffcd433e33">+174/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>AuthProvider.tsx</strong><dd><code>Implement RN AuthProvider
context</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-93f9f27a35d0039a64cd6889a296d04d37542aa5a777925e61e8e60ee5a6d744">+148/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>actions.ts</strong><dd><code>Add Next.js server actions for
Todos</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-76d57097940d3043c8e0ab29761767861b78fe86ab8a90a2d8209f1818131d31">+223/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>signup.tsx</strong><dd><code>Add RN signup tutorial
screen</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-153c2f0bf6b3744bb84b95e356dd78c8771206a1b22218bc4c6f90641e4143ad">+183/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>explore.tsx</strong><dd><code>Add RN tutorial Explore
screen</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-980313b6c7f75f2ecd45fc476895bea122364d000f5988e21564bf5db73d7f57">+125/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>route.ts</strong><dd><code>Add Next.js file download
route</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-5c39cd02d478ad625a7cdb6df3f7b6d20a76f40488636fdd87282c66174b2bd8">+57/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>SignUp.tsx</strong><dd><code>Enhance demo signup with
verification state</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-75657efa0e1c29f59692ced3cd90e9c734836977900dc64015dd5d217bb263da">+38/-3</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>SignUpForm.tsx</strong><dd><code>Add Next.js signup client
form</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-9034cd412c47033a01e604a8250984aa1d1ecefc9884b79ebd7f7f3af17e3167">+89/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>SignInForm.tsx</strong><dd><code>Add Next.js signin client
form</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-c276aadee4507d515832909164d447c3c1a4870277d0adbd1f7e836f7c66259e">+75/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>server.tsx</strong><dd><code>Add server Nhost client for
Next.js</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-9d650defc223584e4aa06ddbf1d9a97c47a5a7ec4c9589a72ac7ea5369853400">+89/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Configuration
changes</strong></td><td><details><summary>1 files</summary><table>
<tr>
<td><strong>_layout.tsx</strong><dd><code>Configure RN tutorial tab
navigation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-c3b0bac088aef9f1a2d5cd2f4b51dd19fb301034668a062860a1e6a3512c15c9">+39/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Additional
files</strong></td><td><details><summary>101 files</summary><table>
<tr>
  <td><strong>examples_tutorials_checks.yaml</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-745712dc409289d30fe4caa74de567df9cc9fac97750ae5566f93a51e48bf539">+94/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>docs.json</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-873ce17c654718debe2fe308a2f2279bde8663686423c51f97fab2dd0722b8d9">+52/-7</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>overview.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-8c9b35da559a5de5fe14ee078573e8d487453e26ed760c03ffd7f0ad476ca24d">+5/-5</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>nextjs.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-a5210d45e7d33a57d43078dbe2a2ccbf0667b157291fd92c3986092d7d33ab9c">+0/-508</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>1-introduction.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-35413705a524a3eb2c7d096daef02d660b479fbc288674aa260e3f159988652b">+116/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>2-protected-routes.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-d901b3a6d8a96e3070f27afb934c34365aa79aeee1505238de2cb77a9ffd8546">+1364/-0</a></td>

</tr>

<tr>
  <td><strong>3-user-authentication.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-7b96576ad1b1b6b8e50dd3889e24df391662df1b7b51a111f0239f655135939f">+804/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>4-graphql-operations.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-16906637a292d22c018167d45f2da67a17fe73155ca161de91c3b329fee9d399">+958/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>5-file-uploads.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-d6153781dbf251499f2ffeeb707102349776d788859c2da0f63c8b0a8e35c821">+822/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>react.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-6f5adda9f7b29d98c68cab6ec754c0bac501666a49dd635ee830789e2c812b68">+0/-497</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>1-introduction.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-4dac916b2b27af668674965d9680f4ba8d2a417f7b155d4b052b1043fc71beb6">+116/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>2-protected-routes.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-8ab0206ae4a4c73e540cf549fdbdd9de4ed7c8d59cc88ea26a4a7ddc0a9a4460">+1435/-0</a></td>

</tr>

<tr>
  <td><strong>3-user-authentication.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-e02466ba1abd5d457724c7508da083a94b16d700cab36384567592c7d772cbf6">+647/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>4-graphql-operations.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-ae759670f7bf513c66bc08952556514e7193f23eb7cc536aebf38ad79e12c02f">+856/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>5-file-uploads.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-02dc5fa1da67b0478bb797ed99c89685be3ec4509023441fb29b730ba74fff4e">+715/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>reactnative.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-a85b3aa1caaeaa0a2cb6219ca531f89aaf2b23b41cd424c189b3cb948af1fd57">+0/-443</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>1-introduction.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-573f696fb008f0e047e97209a1595b1f5c69dd5a1fe4907b65d6883273c53096">+121/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>2-protected-routes.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-b4ab0ba037b62e9eb5ee10cf5f56fd1c148f1e053f95291e42113d9f924ae711">+1499/-0</a></td>

</tr>

<tr>
  <td><strong>3-user-authentication.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-dda89bc27be58e7fa58aef9aeb937dce455f69d9566e734a10ef4e0df993ab42">+805/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>4-graphql-operations.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-59a46fa74310dc797cdfd6e1a34d09fd8002e9accdade9d865b898419addc2bc">+881/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>5-file-uploads.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-cb48bca7fdd422250b2482167b9a136d76ab60d2d5efccb94fb1a022b814d449">+776/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>6-sign-in-with-apple.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-a4da2a574e88122505df17fcf1f9705841a8240b33f60b6fcde86e068b8b6cfe">+684/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>1-introduction.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-1eb8289a9ffc0484d2f8f21ede882c9e892d169f36b69f2ed9af940e2d1b8faa">+116/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>2-protected-routes.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-bff6efc64eeda0dae0d2a133ab6c9be9dfe725ee24b364b89060ec93344ad436">+1308/-0</a></td>

</tr>

<tr>
  <td><strong>3-user-authentication.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-f6bac1bb9476f921272f61911c703529663fb6ff37217c38d1702a1c8b70171a">+578/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>4-graphql-operations.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-3a43d1ebc0b4fd7f74699c4f33ea110bba5ffa2695e6228dccafbfa38798c633">+763/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>5-file-uploads.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-2244e838acdbe909772f82f66cc63800aa9dbf5fae292a40d2d4f663d6618dd4">+642/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>sveltekit.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-9ed4ad735d1142a65a2da2dbbd8d46c508b7aef3d032cdb102d0f329602c4ce1">+0/-497</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>vue.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-f6c4215fa6909fd3accebe0691a7364d17befb8ef90da5a4aeaee83d598c0540">+0/-504</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>1-introduction.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-9485f8a6ad6398b19aa7a3739af3eef5158a7d285dc5a55e8bc6c1d58970fb84">+116/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>2-protected-routes.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-5ba36ef4388ad18b7efc6d405e2b66a69b063d483d4521d6acb5b5da65aee97a">+1368/-0</a></td>

</tr>

<tr>
  <td><strong>3-user-authentication.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-63f56a07c48f78462b47f74420e58cb8411d46aef880cfdf24b11ddea4bb382e">+637/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>4-graphql-operations.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-ad7a84d0c924b946d992f5277cde596fb9d42eff021b888b5a8046822afabca8">+828/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>5-file-uploads.mdx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-00c26651dea4650fd77222a24b31387569067783a87d9c6715935e2bf133428e">+720/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>auth_user_security_keys.yaml</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-385036f3c18b6efd97138842e95f992a0569f612f627e71091c10952d8d31609">+2/-11</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>auth_users.yaml</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-aba47a0c058f0a0a8122dee4d7b4394c4a8997bac9d4ccea04c41f0d00819050">+0/-24</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>storage_files.yaml</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-ea0619f75a1f7dd13ca80d81cc2e28529594a73fff0356dc0b1d49ee0d1cc9ad">+17/-36</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>tables.yaml</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-a57c35142b6c4029fad7d5c8407305a61d18078cd0e264d41286625cf5cc4d30">+1/-5</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>down.sql</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-d7be59389936d96f1c3b10f2147b177d488906d2155139536fae635201859c3e">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>up.sql</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-69c6c175ba9819f7b41d6a27e78fd0390e222665c9cc95fbb7fde702f14a1f4c">+26/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>nhost.toml</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-45587ca86b6441ceacd1318e72a60ee0c30c70c00edb0a06cf061e404998f3bc">+4/-4</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>actions.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-a0279ec5849eb0c93ec7c2f444fbecd2b681f47d954a84da5221f72d9805c2ca">+12/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>page.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-8fffbc65277c93c49eea289ae1174cdba0632e5a295b74668437e9b1b0669097">+61/-30</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>middleware.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-817666e0f29fa78d2d7f77ed93cff4efbc8d85a996ab58d0dd13ad61e47c9125">+5/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>App.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-849f3aa52970f348de49a27094aac4e4b8cb8cf29580cada70d37f1a04249725">+2/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>Navigation.tsx</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-9845c755953914963f404a14104d93cd326f78e48614ece8c4d9df3ab1600ffd">+3/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.css</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-8e415aa656412ff29b45f56035e84691f3abcc924bfb5b4e3cd0c9ba5237543b">+200/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>+page.svelte</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-00da2a0f738ffa5c66a07437c7e85cacb7780551bad7e81899d905f065bf0892">+29/-8</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>main.css</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-1d2ba653024753e96f2db949f818dccba45498456146171c4a2f883b58177d2f">+373/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>Navigation.vue</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-27e2f662a54c47023a1342005bcf92602a846ad7a59e9d7d528aa8fbf40a0250">+7/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>index.ts</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-3bf427bc13a501168d321cf4bd428d09d8c3a042ef3be955c7a2e99bbf5f39dd">+7/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>SignUp.vue</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-1838012876d16508d53d0345615b91c7c6fbccca3c9333d2311ac5fcd7b4c210">+28/-3</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>Todos.vue</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-3334950776983589e7061d69be930b71663329a6ba56726bcbd3f1c2352dab47">+466/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td><strong>Makefile</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-85a3083c78e211e9eb36d741342bcbc85a1a0c375060f45c5426b560196de27f">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>.secrets.example</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-be7a988f18be877344f8584befc094ec2e66fe7784a2007300053707ad8ec7f1">+16/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>Makefile</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-36623bab6fe16382fd3e61b06b9586f2b14bea7c1b492e50db14ea98935016a4">+7/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>README.md</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-ed02e93d01774c3796335c83badc90cbc8a2035040965031946b69c8d91db3dd">+29/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>env-up.sh</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-b6846a8b7ff5eea35ba5ff2aac8773f526c118ef59712a750f69578f25afda54">+8/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>package.json</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-f0d63d57cf77fdb7f2526d10c2d32d51ed2b6d43e3495d3f8879499e0c04f04b">+13/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>tsconfig.json</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-cad0e83f030efa9fcad494c7797838c5099909f2c2ae0de0210812b966ffa6db">+11/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>config.yaml</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-997042f08b088725275a28a7e2d275b86e2e74f3c972800e8116785d5d0fea59">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-3c921023f82fc74f5b657cb92bc8906ea19d7eb8f746713e4fae2a5a894a8a59">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-5f67fb54ebc23bfccd1e23fbb7dc955bad8ad9011fae19369c87bf3f4d3e58fa">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-3c21997bc2db49d5714afc007f214316d17eb69d8ea70dd1cfe558bdb47df4bf">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-e3ab8737d4a9ea68a115f28ce578b7502107f68212d92f035bb86178ccdfb6fe">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-ed34cbd1c3e612ac3124ccce382b84ea7cda7eb22051b07102b68f5c28c58e97">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-b8cead9967aaddc7c52be376fcd54b27c6ec223445e0063729c7255ab6c4f5fc">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-295cddc1774338f5b6ba396ef5ca9a0832b9b2346703ec22abf3b3874f903cc2">+43/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-de3e6c264684c0dddb2f6e70dcb236fe8cb1a27789c01a11b2a707c110ce7fc2">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-ae93f5f97d52d1d42be91b50e2a44522aef884065559f993f781f1ebeaa89b66">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-13511e1d49a2796e2ee0bb2082f02ed59c49bef7f7533735e60ac62ad16cc2c6">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-293b460447946e2de5e4ba74cf64895a4f6fff7d879bb2e555c46ad7edd857a8">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-843300abe10319ac2b6ccffdd8daf3445b8eaa839486acb95566d398cad4e053">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-8ff22c43814b945c0a22ea9089755a144e54039bdd12a2f06a128b4b155b3e38">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-f9e07ca4b5b9ae54e1668c3dfb8ac32612df366eb7d44819163f17c792507295">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-4f829f73c5bce9bcbb67ed1357a111cdf9974c36d2db22493067d201b35f4cdf">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-64f6273ffcec137bbad9a104cd46e6a8776915a804bb3f89c24701071cbb2c50">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-e19bb81615465996e46da50eacac7fddf71dd8bb18200c410b1837575bd76f95">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-a202edc97b202ec41b83decb7b358a6beb89f3edef46194b576ecaada51f073d">+43/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-c2338d16f093d53b8a86b9a859c7bef0d9ef2c83a762d46cd49ab9e2f1f2a773">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-7fff5a89330dd91d3c9781e37d8f1e1f48bfc58c686b463b7fb1f43a2d01813d">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-c9031a1f7ac5ad3d0c3c94cff2f563560c1e3c05d1acc00353e603f3dcac439e">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-847d33f3faf4a7f928445eb389ba8aa24901b9025d4bae0c38becd5e9365a881">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-3f5c827ff0c5a3270dc4c7737b0afd09327800104cb379ed2a556029eb58aef3">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-e499fcf53b46507a64be23ae309eba07e2c3ee289be4de316bd8ee83c16ea42e">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-5a6337adab234155f13e6b7d63eac5c654d5c75f4ea207c9db16b67685ce3e43">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-b8f9c01ea13429ca9990a95e81ac6bedb273ec41198f34903584e9a68c22c6ef">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-5bfe4c29aee11546871f9127b5c5a954f313c64d2344cd4e6cd402fe22cf0ded">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-262bf98a3f4801aff364e19a4c00bf37197764454b274faf1dbd947506b843a7">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-8a4ad52e5c3cd679ede51f7d411ca5aaf89f2f519f49c540e09408bf13823323">+43/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-46f8a60e052ff7b9b3d8583f83f36b826aea9c7a24af9e9998cc0b699900b3b1">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-03da104febe035cf096fccbce0dbbf32197d289de814810792a319154241200c">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-d7fff652e5e21d234b1ab733de23d179d5901ff82165ffbd9495b3a1ab1c7611">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-a84317c8ffe3b56140c923228666555d0e9e93c34eb5a8376b08ab97e6e4ae96">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-5025ef2a43d19c4f668177eea4e8002664a22522e11fef04aabbcdb2fbff3178">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-e941c399a4d0b15de5bf2b20e722c2e6ffc990ecdef15efc2367ae302a52ff63">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-126da45f26e2d9256a28fadd38f363277926de563e6b303027cbea24779c729c">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-4686c2e7f183e071c8ccd456189d9bd056ead4874078dba16b1a2b1e53c7e9e4">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>body.html</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-331385785301bc1e55cfb6a3c707e3704b32d82a38f7ccd9aa4aa834a8569df4">+52/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td><strong>subject.txt</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-57d1b1020e0d2e327acca4154b0e4776f3a7a5db1d6c99edae3ee45561780c6a">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>Additional files not shown</strong></td>
<td><a
href="https://github.com/nhost/nhost/pull/3469/files#diff-2f328e4cd8dbe3ad193e49d92bcf045f47a6b72b1e9487d366f6b8288589b4ca"></a></td>

</tr>
</table></details></td></tr></tr></tbody></table>

</details>

___
2025-09-24 14:50:55 +02:00

1365 lines
30 KiB
Plaintext

---
title: Protecting Routes and Content in Next.js
description: Learn how to protect routes and content in a Next.js application using Nhost authentication.
sidebarTitle: "Protecting Routes"
icon: lock
---
This tutorial part demonstrates how to implement robust route protection in a Next.js application using Nhost authentication. You'll build a complete authentication system with a protected `/profile` page that includes server-side rendering, client components, server actions and API routes. It also features cross-tab session synchronization, and automatic redirects. In addition, we will see how to show conditional navigation and content based on authentication status.
<Info>
This is **Part 2** in the Full-Stack Next.js Development with Nhost series. This part builds a foundation for authentication-protected routes that you can extend to secure any part of your application.
</Info>
## Full-Stack Next.js Development with Nhost
<CardGroup cols={3}>
<Card title="1. Create Project" icon="plus" href="/getting-started/tutorials/nextjs/1-introduction">
Set up your Nhost project
</Card>
<Card title="2. Protected Routes" icon="lock" href="/getting-started/tutorials/nextjs/2-protected-routes">
**Current** - Route protection basics
</Card>
<Card title="3. User Authentication" icon="user" href="/getting-started/tutorials/nextjs/3-user-authentication">
Complete auth flow
</Card>
<Card title="4. GraphQL Operations" icon="list-check" href="/getting-started/tutorials/nextjs/4-graphql-operations">
CRUD operations with GraphQL
</Card>
<Card title="5. File Uploads" icon="upload" href="/getting-started/tutorials/nextjs/5-file-uploads">
File upload and management
</Card>
</CardGroup>
## Prerequisites
- An [Nhost project](/getting-started/tutorials/nextjs/1-introduction) set up
- Node.js 20+ installed
- Basic knowledge of React and Next.js
## Step-by-Step Guide
<Steps>
<Step>
### Create a New Next.js App
We'll start by creating a fresh Next.js application with TypeScript support. Next.js provides server-side rendering, file-based routing, and optimized builds for modern React applications.
```bash
npx create-next-app@15 nhost-nextjs-tutorial --typescript --eslint --app --no-tailwind --yes
cd nhost-nextjs-tutorial
npm install
```
</Step>
<Step>
### Install Required Dependencies
Install the Nhost JavaScript SDK for authentication and session management. The Nhost SDK handles authentication with built-in Next.js support for server-side rendering.
```bash
npm install @nhost/nhost-js
```
</Step>
<Step>
### Environment Configuration
Configure your Nhost project connection by creating environment variables. This allows the app to connect to your specific Nhost backend from both server and client sides.
Create a `.env.local` file in your project root:
```env
NHOST_REGION=<region>
NHOST_SUBDOMAIN=<subdomain>
```
<Warning>
Replace `<region>` and `<subdomain>` with the actual values from your Nhost project dashboard.
</Warning>
</Step>
<Step>
### Create Server-Side Nhost Helper
Create server-side utilities for handling authentication in Next.js server components and middleware. This enables server-side session access and token refresh functionality.
```tsx src/lib/nhost/server.tsx lines
import { createServerClient, type NhostClient } from "@nhost/nhost-js";
import { DEFAULT_SESSION_KEY, type Session } from "@nhost/nhost-js/session";
import { cookies } from "next/headers";
import type { NextRequest, NextResponse } from "next/server";
const key = DEFAULT_SESSION_KEY;
/**
* Creates an Nhost client for use in server components.
*
* We rely on the vanilla createClient method from the Nhost JS SDK and a SessionStorage
* customized to be able to retrieve the session from cookies in Next.js server components.
*/
export async function createNhostClient(): Promise<NhostClient> {
const cookieStore = await cookies();
const nhost = createServerClient({
region: process.env["NHOST_REGION"] || "local",
subdomain: process.env["NHOST_SUBDOMAIN"] || "local",
storage: {
// storage compatible with Next.js server components
get: (): Session | null => {
const s = cookieStore.get(key)?.value || null;
if (!s) {
return null;
}
const session = JSON.parse(s) as Session;
return session;
},
set: (value: Session) => {
cookieStore.set(key, JSON.stringify(value));
},
remove: () => {
cookieStore.delete(key);
},
},
});
return nhost;
}
/**
* Middleware function to handle Nhost authentication and session management.
*
* This function is designed to be used in Next.js middleware to manage user sessions
* and refresh tokens. Refreshing the session needs to be done in the middleware
* to ensure that the session is always up-to-date an accessible by both server and client components.
*
* @param {NextRequest} request - The incoming Next.js request object
* @param {NextResponse} response - The outgoing Next.js response object
*/
export async function handleNhostMiddleware(
request: NextRequest,
response: NextResponse<unknown>,
): Promise<Session | null> {
const nhost = createServerClient({
region: process.env["NHOST_REGION"] || "local",
subdomain: process.env["NHOST_SUBDOMAIN"] || "local",
storage: {
// storage compatible with Next.js middleware
get: (): Session | null => {
const raw = request.cookies.get(key)?.value || null;
if (!raw) {
return null;
}
const session = JSON.parse(raw) as Session;
return session;
},
set: (value: Session) => {
response.cookies.set({
name: key,
value: JSON.stringify(value),
path: "/",
httpOnly: false, //if set to true we can't access it in the client
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 60 * 60 * 24 * 30, // 30 days in seconds
});
},
remove: () => {
response.cookies.delete(key);
},
},
});
// we only want to refresh the session if the token will
// expire in the next 60 seconds
return await nhost.refreshSession(60);
}
```
</Step>
<Step>
### Create Middleware for Route Protection
Create Next.js middleware to handle route protection at the server level. This middleware runs before any page renders and automatically redirects unauthenticated users from protected routes.
```tsx src/middleware.ts lines
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { handleNhostMiddleware } from "./lib/nhost/server";
// Define public routes that don't require authentication
const publicRoutes = ["/"];
export async function middleware(request: NextRequest) {
// Create a response that we'll modify as needed
const response = NextResponse.next();
// Get the current path
const path = request.nextUrl.pathname;
// Check if this is a public route or a public asset
const isPublicRoute = publicRoutes.some(
(route) => path === route || path.startsWith(`${route}/`),
);
// Handle Nhost authentication and token refresh
// Always call this to ensure session is up-to-date
// even for public routes, so that session changes are detected
const session = await handleNhostMiddleware(request, response);
// If it's a public route, allow access without checking auth
if (isPublicRoute) {
return response;
}
// If no session and not a public route, redirect to signin
if (!session) {
const homeUrl = new URL("/", request.url);
return NextResponse.redirect(homeUrl);
}
// Session exists, allow access to protected route
return response;
}
// Define which routes this middleware should run on
export const config = {
matcher: [
/*
* Match all request paths except:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - public files (public directory)
*/
"/((?!_next/static|_next/image|favicon.ico|public).*)",
],
};
```
</Step>
<Step>
### Create the Protected Profile Page
Create a page that displays user information. Note that the page itself doesn't need any special authentication logic - the route protection is handled entirely by the middleware from the previous step.
```tsx src/app/profile/page.tsx lines
import { createNhostClient } from "../../lib/nhost/server";
export default async function Profile() {
// Create the client with async cookie access
const nhost = await createNhostClient();
const session = nhost.getUserSession();
return (
<div className="container">
<header className="page-header">
<h1 className="page-title">Your Profile</h1>
</header>
<div className="form-card">
<h3 className="form-title">User Information</h3>
<div className="form-fields">
<div className="field-group">
<strong>Display Name:</strong>{" "}
{session?.user?.displayName || "Not set"}
</div>
<div className="field-group">
<strong>Email:</strong> {session?.user?.email || "Not available"}
</div>
<div className="field-group">
<strong>User ID:</strong> {session?.user?.id || "Not available"}
</div>
<div className="field-group">
<strong>Roles:</strong> {session?.user?.roles?.join(", ") || "None"}
</div>
<div className="field-group">
<strong>Email Verified:</strong>
<span
className={
session?.user?.emailVerified
? "email-verified"
: "email-unverified"
}
>
{session?.user?.emailVerified ? "✓ Yes" : "✗ No"}
</span>
</div>
</div>
</div>
<div className="form-card">
<h3 className="form-title">Session Information</h3>
<div className="description">
<pre className="session-display">
{JSON.stringify(session, null, 2)}
</pre>
</div>
</div>
</div>
);
}
```
</Step>
<Step>
### Create a Simple Home Page
Build a public homepage that adapts its content based on authentication status. This shows users different options depending on whether they're signed in.
```tsx src/app/page.tsx lines
import { createNhostClient } from "../lib/nhost/server";
export default async function Home() {
const nhost = await createNhostClient();
const session = nhost.getUserSession();
return (
<div className="container">
<header className="page-header">
<h1 className="page-title">Welcome to Nhost Next.js Demo</h1>
</header>
{session ? (
<div>
<p>Hello, {session.user?.displayName || session.user?.email}!</p>
</div>
) : (
<div>
<p>You are not signed in.</p>
</div>
)}
</div>
);
}
```
</Step>
<Step>
### Create the Navigation Component
Create a server-side navigation component that provides consistent navigation across all pages. This component adapts its links based on authentication status - showing different options for signed-in and signed-out users with server-side rendering.
```tsx src/components/Navigation.tsx lines
import Link from "next/link";
import { createNhostClient } from "../lib/nhost/server";
export default async function Navigation() {
const nhost = await createNhostClient();
const session = nhost.getUserSession();
return (
<nav className="navigation">
<div className="nav-container">
<Link href="/" className="nav-logo">
Nhost Next.js Demo
</Link>
<div className="nav-links">
<Link href="/" className="nav-link">
Home
</Link>
{session ? (
<>
<Link href="/profile" className="nav-link">
Profile
</Link>
</>
) : (
<>
Placeholder for signin/signup links
</>
)}
</div>
</div>
</nav>
);
}
```
</Step>
<Step>
### Update the Root Layout
Configure the Next.js root layout to include the navigation and page content.
```tsx src/app/layout.tsx lines
import type { Metadata } from "next";
import "./globals.css";
import Navigation from "../components/Navigation";
export const metadata: Metadata = {
title: "Nhost Next.js Tutorial",
description: "Next.js tutorial with Nhost authentication",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Navigation />
<div className="app-content">{children}</div>
</body>
</html>
);
}
```
</Step>
<Step>
### Add Application Styles
Replace the contents of the file `src/app/global.css` with the following styles to provide a clean and modern look for the application. This file will be used across the rest of series.
```css src/app/globals.css lines
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
}
#root {
width: 100%;
min-height: 100vh;
display: block;
margin: 0;
padding: 0;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
input,
textarea {
width: 100%;
padding: 0.875rem 1rem;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
font-size: 0.875rem;
transition: all 0.2s ease;
background: rgba(255, 255, 255, 0.05);
color: white;
box-sizing: border-box;
font-family: inherit;
}
input:focus,
textarea:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
background: rgba(255, 255, 255, 0.08);
}
input::placeholder,
textarea::placeholder {
color: rgba(255, 255, 255, 0.5);
}
textarea {
resize: vertical;
min-height: 4rem;
}
label {
display: block;
margin: 0 0 0.5rem 0;
font-weight: 500;
color: rgba(255, 255, 255, 0.9);
font-size: 0.875rem;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Global Layout */
.app-content {
padding: 0 2rem 2rem;
max-width: 800px;
margin: 0 auto;
}
.page-center {
text-align: center;
padding: 2rem;
}
.page-header {
margin-bottom: 2rem;
}
.page-title {
font-weight: 700;
margin: 0;
display: flex;
align-items: center;
gap: 1rem;
}
.margin-bottom {
margin-bottom: 1rem;
}
.margin-top {
margin-top: 1rem;
}
.container {
width: 800px;
max-width: calc(100vw - 4rem);
min-width: 320px;
margin: 0 auto;
padding: 2rem;
box-sizing: border-box;
position: relative;
}
/* Status Messages */
.success-message {
padding: 1rem;
background-color: #d4edda;
color: #155724;
border-radius: 8px;
margin-bottom: 1rem;
}
.error-message {
background: rgba(239, 68, 68, 0.1);
color: #ef4444;
border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: 12px;
padding: 1rem 1.5rem;
margin: 1rem 0;
}
.help-text {
color: #666;
}
.verification-status {
color: #28a745;
font-size: 1.2rem;
font-weight: bold;
margin-bottom: 1rem;
}
.verification-status.error {
color: #dc3545;
font-size: 1.1rem;
}
/* Email Verification Status */
.email-verified {
color: #10b981;
font-weight: bold;
margin-left: 0.5rem;
}
.email-unverified {
color: #ef4444;
font-weight: bold;
margin-left: 0.5rem;
}
/* Debug Info */
.debug-panel {
margin-bottom: 1rem;
padding: 1rem;
background-color: #f8f9fa;
border-radius: 8px;
text-align: left;
max-height: 200px;
overflow: auto;
}
.debug-title {
font-weight: bold;
margin-bottom: 0.5rem;
}
.debug-item {
margin-bottom: 0.25rem;
}
.debug-key {
font-family: monospace;
color: #007bff;
}
.debug-value {
font-family: monospace;
}
/* Session Display */
.session-display {
font-size: 0.75rem;
overflow: auto;
margin: 0;
line-height: 1.4;
white-space: pre-wrap;
word-break: break-word;
}
/* Loading Spinner */
.spinner-verify {
width: 32px;
height: 32px;
border: 3px solid #f3f3f3;
border-top: 3px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto;
}
/* Navigation */
.navigation {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
position: sticky;
top: 0;
z-index: 100;
margin-bottom: 2rem;
}
.nav-container {
max-width: 800px;
margin: 0 auto;
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.nav-logo {
font-size: 1.25rem;
font-weight: 700;
color: white;
text-decoration: none;
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.nav-logo:hover {
opacity: 0.8;
}
.nav-links {
display: flex;
align-items: center;
gap: 1.5rem;
}
.nav-link {
color: rgba(255, 255, 255, 0.8);
text-decoration: none;
font-weight: 500;
font-size: 0.875rem;
padding: 0.5rem 0.75rem;
border-radius: 6px;
transition: all 0.2s ease;
border: none;
background: none;
cursor: pointer;
font-family: inherit;
}
.nav-link:hover {
color: white;
background: rgba(255, 255, 255, 0.1);
}
.nav-button {
color: #ef4444;
}
.nav-button:hover {
background: rgba(239, 68, 68, 0.2);
}
/* Buttons */
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.875rem;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
min-width: 120px;
}
.btn-primary {
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.1);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.2);
}
.btn-cancel {
background: rgba(239, 68, 68, 0.1);
color: #ef4444;
border: 1px solid rgba(239, 68, 68, 0.3);
}
.btn-cancel:hover {
background: rgba(239, 68, 68, 0.2);
}
/* Loading State */
.loading-container {
display: flex;
justify-content: center;
align-items: center;
padding: 4rem 2rem;
}
.loading-content {
display: flex;
align-items: center;
gap: 1rem;
}
.spinner {
width: 2rem;
height: 2rem;
border: 3px solid rgba(59, 130, 246, 0.3);
border-top: 3px solid #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.loading-text {
color: rgba(255, 255, 255, 0.7);
font-size: 0.875rem;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 4rem 2rem;
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
}
.empty-icon {
width: 4rem;
height: 4rem;
color: rgba(255, 255, 255, 0.4);
margin: 0 auto 1rem;
}
.empty-title {
font-size: 1.25rem;
font-weight: 600;
color: white;
margin: 0 0 0.5rem 0;
}
.empty-description {
color: rgba(255, 255, 255, 0.6);
margin: 0;
}
/* Forms */
.form-card {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
padding: 2rem;
margin-bottom: 2rem;
width: 100%;
box-sizing: border-box;
}
.form-title {
font-size: 1.5rem;
font-weight: 600;
color: white;
margin: 0 0 1.5rem 0;
}
.form-fields {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.field-group {
display: flex;
flex-direction: column;
}
.form-actions {
display: flex;
gap: 1rem;
margin-top: 1rem;
}
/* Auth Pages */
.auth-form {
max-width: 400px;
}
.auth-form-field {
margin-bottom: 1rem;
}
.auth-input {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
}
.auth-error {
color: red;
margin-bottom: 1rem;
padding: 0.5rem;
background-color: #fee;
border-radius: 4px;
}
.auth-button {
width: 100%;
padding: 0.75rem;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.auth-button:disabled {
cursor: not-allowed;
}
.auth-button.primary {
background-color: #28a745;
}
.auth-button.secondary {
background-color: #007bff;
}
.auth-links {
margin-top: 1rem;
}
/* Todos */
.todo-form {
width: 100%;
}
/* Todo List */
.todos-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.todo-card {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
transition: all 0.2s ease;
overflow: hidden;
width: 100%;
box-sizing: border-box;
}
.todo-card:hover {
border-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.todo-card.completed {
opacity: 0.7;
}
/* Todo Content */
.todo-content {
padding: 1rem 1.5rem;
}
.todo-edit {
padding: 1.5rem;
min-height: 200px;
}
.edit-fields {
display: flex;
flex-direction: column;
gap: 1rem;
}
.edit-actions {
display: flex;
gap: 1rem;
margin-top: 1.5rem;
}
.todo-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
}
.todo-title-btn {
background: none;
border: none;
padding: 0;
text-align: left;
font-size: 1.25rem;
font-weight: 600;
color: white;
cursor: pointer;
transition: color 0.2s ease;
flex: 1;
line-height: 1.4;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: calc(100% - 140px);
}
.todo-title-btn:hover {
color: #3b82f6;
}
.todo-title-btn.completed {
text-decoration: line-through;
color: rgba(255, 255, 255, 0.5);
}
.todo-actions {
display: flex;
gap: 0.5rem;
flex-shrink: 0;
min-width: 132px;
justify-content: flex-end;
}
/* Action Buttons */
.action-btn {
width: 40px;
height: 40px;
border: none;
border-radius: 6px;
cursor: pointer;
background: rgba(255, 255, 255, 0.05);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
transition: all 0.2s ease;
-webkit-text-fill-color: currentColor;
}
.action-btn-complete {
color: #10b981;
font-size: 20px;
}
.action-btn-complete:hover {
background: rgba(16, 185, 129, 0.2);
color: #34d399;
}
.action-btn-edit {
color: #3b82f6;
}
.action-btn-edit:hover {
background: rgba(59, 130, 246, 0.2);
color: #60a5fa;
}
.action-btn-delete {
color: #ef4444;
}
.action-btn-delete:hover {
background: rgba(239, 68, 68, 0.2);
color: #f87171;
}
/* Add Todo Button */
.add-todo-btn {
width: 36px;
height: 36px;
border: none;
border-radius: 6px;
cursor: pointer;
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 18px;
font-weight: normal;
-webkit-text-fill-color: white;
transition: all 0.2s ease;
}
.add-todo-btn:hover {
transform: scale(1.1);
box-shadow: 0 4px 20px rgba(59, 130, 246, 0.4);
}
/* Todo Details */
.todo-details {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.description {
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
}
.description p {
margin: 0;
color: rgba(255, 255, 255, 0.8);
line-height: 1.6;
}
.description.completed p {
text-decoration: line-through;
color: rgba(255, 255, 255, 0.5);
}
.todo-meta {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
}
.meta-dates {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.meta-item {
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.5);
}
.completion-badge {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.75rem;
color: #10b981;
font-weight: 500;
}
.completion-icon {
width: 0.875rem;
height: 0.875rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.nav-container {
padding: 1rem;
flex-direction: column;
gap: 1rem;
}
.nav-links {
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
}
.container {
padding: 1rem;
}
.form-actions {
flex-direction: column;
}
.edit-actions {
flex-direction: column;
}
.todo-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.todo-actions {
align-self: stretch;
justify-content: center;
}
.meta-dates {
flex-direction: column;
gap: 0.25rem;
}
.todo-meta {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
}
/* File Upload */
.file-upload-btn {
min-height: 120px;
flex-direction: column;
gap: 0.5rem;
width: 100%;
border: 2px dashed rgba(255, 255, 255, 0.3);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.file-upload-btn:hover {
border-color: rgba(255, 255, 255, 0.5);
background-color: rgba(255, 255, 255, 0.05);
}
.file-upload-info {
margin-top: 0.5rem;
font-size: 0.875rem;
color: rgba(255, 255, 255, 0.8);
}
/* File Table */
.file-table {
width: 100%;
border-collapse: collapse;
}
.file-table th {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.7);
font-weight: 500;
font-size: 0.875rem;
}
.file-table th:last-child {
text-align: center;
}
.file-table td {
padding: 0.75rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.file-table tr:hover {
background-color: rgba(255, 255, 255, 0.02);
}
.file-name {
color: white;
font-weight: 500;
}
.file-meta {
color: rgba(255, 255, 255, 0.6);
font-size: 0.875rem;
}
.file-actions {
display: flex;
gap: 0.5rem;
justify-content: center;
}
/* Responsive File Table */
@media (max-width: 768px) {
.file-table {
font-size: 0.875rem;
}
.file-table th,
.file-table td {
padding: 0.5rem;
}
.file-actions {
flex-direction: column;
gap: 0.25rem;
}
}
```
</Step>
<Step>
### Run and test the Application
Start the development server to test your route protection implementation:
```bash
npm run dev
```
Things to try out:
1. Try navigating to `/profile` - you should be redirected to the homepage `/` since you're not authenticated.
2. Because you are not signed in, the navigation bar should only show the "Home" link and the placeholder for signin/signup links.
3. On the homepage, you should see a message indicating that you are not signed in.
After we complete the next tutorial on user authentication, you will be able to sign in and access the protected `/profile` page and see how the navigation bar and homepage updates accordingly.
</Step>
</Steps>
## How It Works
1. **Server-Side Helpers**: Utilities for handling authentication in Next.js server components and middleware
2. **Middleware Route Protection**: Next.js middleware runs before any page renders, automatically redirecting unauthenticated users from protected routes and refreshing tokens
3. **AuthProvider**: Client-side provider that manages authentication state using Nhost's client with cookie-based storage for server/client synchronization
4. **Protected Pages**: Server components can assume authentication since middleware handles protection, focusing purely on rendering authenticated content
5. **Navigation**: Server-side navigation component that adapts its links based on authentication status
6. **Automatic Redirects**: All route protection and redirects are handled at the middleware level for optimal performance and security
## Key Features Demonstrated
<AccordionGroup>
<Accordion title="Middleware Route Protection" icon="shield-check">
Routes are protected at the middleware level, running before any page renders to provide the highest level of security and performance by preventing unauthorized access before components load.
</Accordion>
<Accordion title="No Loading States" icon="zap">
Server-side authentication checks eliminate the need for loading spinners during authentication verification, providing instant feedback to users.
</Accordion>
<Accordion title="Automatic Server Redirects" icon="arrow-right">
Users are automatically redirected based on their authentication status using Next.js server-side redirects, ensuring proper navigation flow before any client-side rendering.
</Accordion>
<Accordion title="Cross-tab Synchronization" icon="arrows-rotate">
Authentication state is synchronized across multiple browser tabs using Nhost's session storage events with Next.js router refresh for server-side state updates.
</Accordion>
<Accordion title="Cookie-Based Session Management" icon="circle-user">
Complete user session and profile information is shared between server and client using cookie-based storage, enabling seamless SSR and client-side functionality.
</Accordion>
<Accordion title="Server-Side Rendering" icon="server">
Authentication-dependent content is rendered server-side for improved SEO, performance, and security compared to client-only approaches.
</Accordion>
<Accordion title="Automatic Token Refresh" icon="refresh">
Middleware automatically refreshes authentication tokens before they expire, ensuring seamless user experience without manual token management.
</Accordion>
</AccordionGroup>