Skip to content

Commit

Permalink
Merge pull request #1 from KyleSmith0905/use-nuxt-route-rules
Browse files Browse the repository at this point in the history
Added route-based rules for cache-support.
  • Loading branch information
KyleSmith0905 authored Dec 31, 2023
2 parents e6c1251 + 21ec51e commit 53e82fd
Show file tree
Hide file tree
Showing 21 changed files with 343 additions and 17 deletions.
85 changes: 85 additions & 0 deletions docs/content/1.getting-started/4.caching-content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
description: "Learn how to configure your project to support caching"
---

::alert{type="info"}
If you are using the following routeRules (`swr`, `isr`, `prerender`), you will need to read this. When prerendering your entire site using `nuxi generate`, this is done automatically.
::

# Caching Content

Often hosting providers offer caching on the edge. Most websites can experience incredible speeds (and cost savings) by taking advantage of caching. No cold start, no processing requests, no parsing Javascript... just HTML served immediately from a CDN.

By default we send the user's authentication data down to the client in the HTML. This might not be ideal if you're caching your pages. Users may be able to see other user's authentication data if not handled properly.

To add caching to your Nuxt app, follow the [Nuxt documentation on hybrid rendering](https://nuxt.com/docs/guide/concepts/rendering#hybrid-rendering).

## Configuration

::alert{type="warning"}
If you find yourself needing to server-rendered auth methods like `getProviders()`, you must set the `baseURL` option on the `auth` object. This applies in development too.
::

### Page Specific Cache Rules

If only a few of your pages are cached. Head over to the Nuxt config `routeRules`, add the `auth` key to your cached routes. Set `disableServerSideAuth` to `true`.

```ts
export default defineNuxtConfig({
modules: ['@sidebase/nuxt-auth'],
auth: {
// Optional - Needed for getProviders() method to work server-side
baseURL: 'http://localhost:3000',
},
routeRules: {
'/': {
swr: 86400000,
auth: {
disableServerSideAuth: true,
},
},
},
})
```

### Module Cache Rules

If all/most pages on your site are cached. Head over to the Nuxt config, add the `auth` key if not already there. Set `disableServerSideAuth` to `true`.

```ts
export default defineNuxtConfig({
modules: ['@sidebase/nuxt-auth'],
auth: {
disableServerSideAuth: true,
// Optional - Needed for getProviders() method to work server-side
baseURL: 'http://localhost:3000',
},
})
```

### Combining Configurations

Route-configured options take precedent over module-configured options. If you disabled server side auth in the module, you may still enable server side auth back by setting `auth.disableServerSideAuth` to `false`.

For example: It may be ideal to add caching to every page besides your profile page.

```ts
export default defineNuxtConfig({
modules: ['@sidebase/nuxt-auth'],
auth: {
disableServerSideAuth: true,
},
routeRules: {
// Server side auth is disabled on this page
'/': {
swr: 86400000,
}
// Server side auth is enabled on this page - route rules takes priority.
'/profile': {
auth: {
disableServerSideAuth: false,
},
},
},
})
```
6 changes: 4 additions & 2 deletions docs/content/2.configuration/2.nuxt-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ interface ModuleOptions {
isEnabled?: boolean
/**
* Forces your server to send a "loading" status on all requests, prompting the client to fetch on the client. If your website has caching, this prevents the server from caching someone's authentication status.
*
*
* This effects the entire site, for route-specific rules, add `disableServerSideAuth` on `routeRules`.
*
* @default false
*/
disableServerSideAuth?: boolean
disableServerSideAuth?: boolean;
/**
* Full url at which the app will run combined with the path to authentication. You can set this differently depending on your selected authentication-provider:
* - `authjs`: You must set the full URL, with origin and path in production. You can leave this empty in development
Expand Down
14 changes: 14 additions & 0 deletions docs/content/2.configuration/3.route-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Route Rules

Use the `auth`-key inside the `nuxt.config.ts` `routeRules` to configure page-specific settings.

```ts
interface RouteOptions {
/**
* Forces your server to send a "loading" status on a route, prompting the client to fetch on the client. If a specific page has caching, this prevents the server from caching someone's authentication status.
*
* @default falses
*/
disableServerSideAuth: boolean;
}
```
9 changes: 9 additions & 0 deletions playground-authjs/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ export default defineNuxtConfig({
},
globalAppMiddleware: {
isEnabled: true
},
baseURL: 'http://localhost:3000'
},
routeRules: {
'/with-caching': {
swr: 86400000,
auth: {
disableServerSideAuth: true
}
}
}
})
4 changes: 4 additions & 0 deletions playground-authjs/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ definePageMeta({ auth: false })
-> guest mode
</nuxt-link>
<br>
<nuxt-link to="/with-caching">
-> cached page with swr
</nuxt-link>
<br>
<div>select one of the above actions to get started.</div>
</div>
</template>
31 changes: 31 additions & 0 deletions playground-authjs/pages/with-caching.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script setup lang="ts">
import { computed, definePageMeta, onMounted, ref } from '#imports'
const cachedAt = ref(new Date())
const enteredAt = ref<Date | undefined>(undefined)
const cachedAtTime = computed(() => cachedAt.value.getTime())
const enteredAtTime = computed(() => (enteredAt.value?.getTime() ?? 0))
const relativeTimeFormat = ref(new Intl.RelativeTimeFormat('en'))
onMounted(() => {
enteredAt.value = new Date()
})
definePageMeta({ auth: false })
</script>

<template>
<div>
<p v-if="cachedAtTime < enteredAtTime - 5000">
This page was cached
{{ relativeTimeFormat.format((cachedAtTime / 60000) - (enteredAtTime / 60000), 'minutes') }}.
</p>
<p v-else>
This page was not cached.
</p>
<p>Cached At: {{ enteredAt?.toISOString() }}.</p>
<p>Created At: {{ enteredAt?.toISOString() }}.</p>
</div>
</template>
8 changes: 8 additions & 0 deletions playground-local/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,13 @@ export default defineNuxtConfig({
globalAppMiddleware: {
isEnabled: true
}
},
routeRules: {
'/with-caching': {
swr: 86400000,
auth: {
disableServerSideAuth: true
}
}
}
})
4 changes: 4 additions & 0 deletions playground-local/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ definePageMeta({ auth: false })
-> guest mode
</nuxt-link>
<br>
<nuxt-link to="/with-caching">
-> cached page with swr
</nuxt-link>
<br>
<div>select one of the above actions to get started.</div>
</div>
</template>
31 changes: 31 additions & 0 deletions playground-local/pages/with-caching.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script setup lang="ts">
import { computed, definePageMeta, onMounted, ref } from '#imports'
const cachedAt = ref(new Date())
const enteredAt = ref<Date | undefined>(undefined)
const cachedAtTime = computed(() => cachedAt.value.getTime())
const enteredAtTime = computed(() => (enteredAt.value?.getTime() ?? 0))
const relativeTimeFormat = ref(new Intl.RelativeTimeFormat('en'))
onMounted(() => {
enteredAt.value = new Date()
})
definePageMeta({ auth: false })
</script>

<template>
<div>
<p v-if="cachedAtTime < enteredAtTime - 5000">
This page was cached
{{ relativeTimeFormat.format((cachedAtTime / 60000) - (enteredAtTime / 60000), 'minutes') }}.
</p>
<p v-else>
This page was not cached.
</p>
<p>Cached At: {{ enteredAt?.toISOString() }}.</p>
<p>Created At: {{ enteredAt?.toISOString() }}.</p>
</div>
</template>
11 changes: 11 additions & 0 deletions playground-refresh/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,16 @@ export default defineNuxtConfig({
globalAppMiddleware: {
isEnabled: true
}
},
routeRules: {
'/': {
swr: 86400000
},
'/with-caching': {
swr: 86400000,
auth: {
disableServerSideAuth: true
}
}
}
})
4 changes: 4 additions & 0 deletions playground-refresh/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ definePageMeta({ auth: false })
-> guest mode
</nuxt-link>
<br>
<nuxt-link to="/with-caching">
-> cached page with swr
</nuxt-link>
<br>
<div>select one of the above actions to get started.</div>
</div>
</template>
31 changes: 31 additions & 0 deletions playground-refresh/pages/with-caching.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script setup lang="ts">
import { computed, definePageMeta, onMounted, ref } from '#imports'
const cachedAt = ref(new Date())
const enteredAt = ref<Date | undefined>(undefined)
const cachedAtTime = computed(() => cachedAt.value.getTime())
const enteredAtTime = computed(() => (enteredAt.value?.getTime() ?? 0))
const relativeTimeFormat = ref(new Intl.RelativeTimeFormat('en'))
onMounted(() => {
enteredAt.value = new Date()
})
definePageMeta({ auth: false })
</script>

<template>
<div>
<p v-if="cachedAtTime < enteredAtTime - 5000">
This page was cached
{{ relativeTimeFormat.format((cachedAtTime / 60000) - (enteredAtTime / 60000), 'minutes') }}.
</p>
<p v-else>
This page was not cached.
</p>
<p>Cached At: {{ enteredAt?.toISOString() }}.</p>
<p>Created At: {{ enteredAt?.toISOString() }}.</p>
</div>
</template>
11 changes: 10 additions & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,16 @@ export default defineNuxtModule<ModuleOptions>({
(options.provider as any).sessionDataType
)
: '',
'}'
'}',
"declare module 'nitropack' {",
' interface NitroRouteRules {',
` auth?: import('${resolve('./runtime/types.ts')}').RouteOptions`,
' }',
' interface NitroRouteConfig {',
` auth?: import('${resolve('./runtime/types.ts')}').RouteOptions`,
' }',
'}',
'export {}'
].join('\n')
})

Expand Down
12 changes: 9 additions & 3 deletions src/runtime/composables/local/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CommonUseAuthReturn, SignOutFunc, SignInFunc, GetSessionFunc, Secondary
import { _fetch } from '../../utils/fetch'
import { jsonPointerGet, useTypedBackendConfig } from '../../helpers'
import { getRequestURLWN } from '../../utils/callWithNuxt'
import { formatToken } from '../../utils/local'
import { useAuthState } from './useAuthState'
// @ts-expect-error - #auth not defined
import type { SessionData } from '#auth'
Expand Down Expand Up @@ -74,13 +75,18 @@ const getSession: GetSessionFunc<SessionData | null | void> = async (getSessionO

const config = useTypedBackendConfig(useRuntimeConfig(), 'local')
const { path, method } = config.endpoints.getSession
const { data, loading, lastRefreshedAt, token, rawToken } = useAuthState()
const { data, loading, lastRefreshedAt, rawToken, token: tokenState, _internal } = useAuthState()

if (!token.value && !getSessionOptions?.force) {
let token = tokenState.value
// For cached responses, return the token directly from the token
token ??= formatToken(_internal.rawTokenCookie.value)

if (!token && !getSessionOptions?.force) {
loading.value = false
return
}

const headers = new Headers(token.value ? { [config.token.headerName]: token.value } as HeadersInit : undefined)
const headers = new Headers(token ? { [config.token.headerName]: token } as HeadersInit : undefined)

loading.value = true
try {
Expand Down
27 changes: 19 additions & 8 deletions src/runtime/composables/local/useAuthState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import type { CookieRef } from '#app'
import { CommonUseAuthStateReturn } from '../../types'
import { makeCommonAuthState } from '../commonAuthState'
import { useTypedBackendConfig } from '../../helpers'
import { useRuntimeConfig, useCookie, useState } from '#imports'
import { formatToken } from '../../utils/local'
import { useRuntimeConfig, useCookie, useState, onMounted } from '#imports'
// @ts-expect-error - #auth not defined
import type { SessionData } from '#auth'

Expand All @@ -12,6 +13,10 @@ interface UseAuthStateReturn extends CommonUseAuthStateReturn<SessionData> {
rawToken: CookieRef<string | null>,
setToken: (newToken: string | null) => void
clearToken: () => void
_internal: {
baseURL: string,
rawTokenCookie: CookieRef<string | null>
}
}

export const useAuthState = (): UseAuthStateReturn => {
Expand All @@ -24,12 +29,7 @@ export const useAuthState = (): UseAuthStateReturn => {
const rawToken = useState('auth:raw-token', () => _rawTokenCookie.value)
watch(rawToken, () => { _rawTokenCookie.value = rawToken.value })

const token = computed(() => {
if (rawToken.value === null) {
return null
}
return config.token.type.length > 0 ? `${config.token.type} ${rawToken.value}` : rawToken.value
})
const token = computed(() => formatToken(rawToken.value))

const setToken = (newToken: string | null) => {
rawToken.value = newToken
Expand All @@ -44,11 +44,22 @@ export const useAuthState = (): UseAuthStateReturn => {
rawToken
}

onMounted(() => {
// When the page is cached on a server, set the token on the client
if (_rawTokenCookie.value && !rawToken.value) {
setToken(_rawTokenCookie.value)
}
})

return {
...commonAuthState,
...schemeSpecificState,
setToken,
clearToken
clearToken,
_internal: {
...commonAuthState._internal,
rawTokenCookie: _rawTokenCookie
}
}
}
export default useAuthState
Loading

0 comments on commit 53e82fd

Please sign in to comment.