Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions apps/docs/content/guides/storage/cdn/smart-cdn.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ If you anticipate that your asset might be deleted, it's advisable to set a shor

## Bypassing cache

If you need to ensure assets refresh directly from the origin server and bypass the cache, you can achieve this by adding a unique query string to the URL.
If you need to ensure assets refresh directly from the origin server and bypass the cache, you can achieve this by adding a unique query string such as `cacheNonce` to the URL.

For instance, you can use a URL like `/storage/v1/object/sign/profile-pictures/cat.jpg?version=1` with a long browser cache (e.g., 1 year). To update the picture, increment the version query parameter in the URL, like `/storage/v1/object/sign/profile-pictures/cat.jpg?version=2`. The CDN will recognize it as a new object and fetch the updated version from the origin.
For instance, you can use a URL like `/storage/v1/object/sign/profile-pictures/cat.jpg?cacheNonce=1` with a long browser cache (e.g., 1 year). To update the picture, increment the `cacheNonce` query parameter in the URL, like `/storage/v1/object/sign/profile-pictures/cat.jpg?cacheNonce=2`. The CDN will recognize it as a new object and fetch the updated version from the origin.

## Signed URLs and CDN caching

Signed URLs are the primary way to serve assets from private buckets to end users. With Smart CDN enabled, signed URL responses are cached at the CDN edge, just like any other storage request.

Unlike public bucket URLs, each signed URL contains a unique token query parameter (`?token=...`). Smart CDN treats each unique token as a separate cache key, meaning the first request with any given signed URL results in a cache miss, and only subsequent requests using that exact same URL will receive a cache hit. Two different signed URLs for the same object, even if generated seconds apart, each maintain their own independent cache entry.

This affects how you should think about serving private assets:

- If you generate a new signed URL on every request, the cache will never be warm and every request hits the origin.
- If you re-use the same signed URL across multiple requests, subsequent requests are served from cache. If the asset has no meaningful per-user access restriction, prefer a public bucket. It results in higher cache hit rates and removes the need to manage signed URL generation entirely.
- Revoking or expiring a token does not purge its CDN cache entry. The cached response persists at the edge until the cache duration expires.
- Deleting the object invalidates all cached entries for that object across all tokens. This can take up to a minute to propagate.

Token expiry (`expiresIn`) and the object's response cache TTL (`cacheControl`) are independent. Once a response is cached at the edge, that cached response can continue to be served for the same signed URL until the CDN cache duration expires, even if the token in that URL has already expired. If you need to cut off access to an asset, delete the object from the bucket rather than relying on token expiry alone.
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useParams } from 'common'
import { Card, CardContent, cn } from 'ui'
import {
PageSection,
PageSectionContent,
PageSectionDescription,
PageSectionMeta,
PageSectionSummary,
PageSectionTitle,
} from 'ui-patterns'
import { Input } from 'ui-patterns/DataInputs/Input'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'

import { useOpenIDConfigurationQuery } from '@/data/oauth-server-apps/oauth-openid-configuration-query'

interface OAuthEndpointsTableProps {
isLoading?: boolean
className?: string
}

export const OAuthEndpointsTable = ({
isLoading: isLoadingProp = false,
className,
}: OAuthEndpointsTableProps) => {
const { ref: projectRef } = useParams()

const { data: openidConfig, isLoading: isEndpointsLoading } = useOpenIDConfigurationQuery(
{ projectRef },
{ enabled: !isLoadingProp }
)

const isLoading = isLoadingProp || isEndpointsLoading

const endpoints = [
{
name: 'Authorization endpoint',
value: openidConfig?.authorization_endpoint,
},
{
name: 'Token endpoint',
value: openidConfig?.token_endpoint,
},
{
name: 'JWKS endpoint',
value: openidConfig?.jwks_uri,
},
{
name: 'OIDC discovery',
value: openidConfig?.issuer
? `${openidConfig.issuer}/.well-known/openid-configuration`
: undefined,
},
]

return (
<PageSection className={cn(className)}>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>OAuth Endpoints</PageSectionTitle>
<PageSectionDescription>
Share these endpoints with third-party applications that need to integrate with your
OAuth 2.1 server.
</PageSectionDescription>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
<Card>
<CardContent className="flex flex-col gap-4 pt-0 divide-y">
{isLoading ? (
<GenericSkeletonLoader className="mt-4" />
) : (
endpoints.map((endpoint) => (
<FormItemLayout
key={endpoint.name}
layout="horizontal"
isReactForm={false}
label={endpoint.name}
className="mt-4"
>
<Input readOnly copy value={endpoint.value ?? ''} />
</FormItemLayout>
))
)}
</CardContent>
</Card>
</PageSectionContent>
</PageSection>
)
}
Loading
Loading