Tags: linux nodejs
Here come some user components or modifications for adding features on Astro site.
User Components
Adding new tweaks into Astro framework.
Recent Posts
Create a component:
import { CardGrid , LinkCard } from "@astrojs/starlight/components" ;
let allPosts = await Astro . glob ( "../content/docs/blog/**/[!^_]*.mdx" );
allPosts = allPosts . filter (( post ) => post . frontmatter . slug );
a . frontmatter . date > b . frontmatter . date ? - 1 :
a . frontmatter . date < b . frontmatter . date ? 1 : 0 );
title = { post . frontmatter . title }
description = { post . frontmatter . excerpt }
href = { post . frontmatter . slug }
Declare alias path for user components:
"extends" : "astro/tsconfigs/strict" ,
"@components/*" : [ "src/components/*" ],
"@assets/*" : [ "src/assets/*" ]
Use it in posts:
import RecentPosts from '@components/RecentPosts.astro' ;
Clerk Profile
Create a component:
import { SignedIn , SignedOut , UserButton } from "@clerk/astro/components" ;
import { Icon } from '@astrojs/starlight/components' ;
< a class = "clerk sl-flex" href = "/signin" >
< Icon name = "github" size = "1.5rem" color = "var(--sl-color-text-accent)" />
< div class = "clerk sl-flex" >
clerk-signed-in, clerk-signed-out {
margin - inline - start : 0.5 rem ;
Site Title with Clerk Profile
Refer: https://starlight.astro.build/guides/overriding-components/ .
import type { Props } from '@astrojs/starlight/props' ;
import Default from '@astrojs/starlight/components/SiteTitle.astro' ;
import Clerk from './ClerkProfile.astro' ;
< Default { ... Astro . props } >< slot /></ Default >
Then tell Starlight to use new component instead the old one:
export default defineConfig ({
SiteTitle: './src/components/SiteTitleWithClerkProfile.astro' ,
Post Info
Create a component:
const frontmatter = Astro . props . frontmatter ;
{ frontmatter . tags . length > 0 ? (
{ Astro . locals . t ( 'starlightBlog.post.tags' ) }
{ frontmatter . tags . map (( tag : string ) => (
< a href = { "/blog/tags/" + tag }
border: 1px solid var(--sl-color-gray-5);
font-size: var(--sl-text-sm);
padding: 0.25rem 0.5rem 0.35rem;
< p > { frontmatter . excerpt } </ p >
Then use it in posts:
import PostInfo from '@components/PostInfo.astro' ;
< PostInfo frontmatter = { frontmatter } />
Project Configurations
When using some Starlight plugins, note the integration order:
export default defineConfig ({
SiteTitle: './src/components/SiteTitleWithClerkProfile.astro' ,
starlightImageZoom (), // apply content modifier first
starlightThemeRapide (), // then apply theme
starlightViewModes (), // then apply view modes
starlightBlog (), // then apply features
starlightLinksValidator ({ // for production build
errorOnLocalLinks: false ,
Middleware
Async method
export const onRequest = clerkMiddleware ( async ( auth , context , next ) => {
const data = await loadSomething ();
Use context.locals
Refer: https://docs.astro.build/en/guides/middleware/#storing-data-in-contextlocals .
Using Typescript, some datatype need to be declared to make build done.
For example, below method fails to know premium
property:
export const onRequest = clerkMiddleware ( async ( auth , context , next ) => {
onst user = await context . locals . currentUser ();
context . locals . premium = user ?. publicMetadata . premium ;
src/middleware.ts:20:30 - error ts(2339): Property 'premium' does not exist on type 'Locals'.
To fix this, declare property premium
for App.Locals
:
premium : boolean | undefined ;
Now, there is mismatch type between Locals.premium
and UserPublicMetadata.premium
.
Clerk defines UserPublicMetadata’s properties as unknown type:
* If you want to provide custom types for the user.publicMetadata object,
* simply redeclare this rule in the global namespace.
* Every user object will use the provided type.
interface UserPublicMetadata {
So, just re-declare it:
interface UserPublicMetadata {
premium : boolean | undefined ;
Finally, Locals.premium
is avaible in Astro files:
const premium = Astro . locals . premium ;
Please < a href = "/signin" > sign in </ a > to view the content.
Dynamic Route for Protected Pages
A content collection is any top-level directory inside the reserved src/content project directory, such as src/content/docs
and src/content/protected
.
If a collection needs a schema, it should be defined in src/content/config.ts
.
Create a sample post:
export const prerender = false ;
import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro" ;
import Premium from '@components/Premium.astro'
< StarlightPage frontmatter = { frontmatter } >
Content collections are stored outside of the src/pages
directory.
This means that no routes are generated for collection items by default.
Create dynamic route:
import { getEntry } from "astro:content" ;
// 1. Get the slug from the incoming server request
const { slug } = Astro . params ;
if ( slug === undefined ) {
throw new Error ( "Slug is required" );
// 2. Query for the entry directly using the requested slug
const entry = await getEntry ( "protected" , slug );
// 3. Redirect if the entry does not exist
if ( entry === undefined ) {
return Astro . redirect ( "/404" );
// 4. (Optional) Render the entry to HTML in the template
const { Content } = await entry . render ();
// 5. (Optional) Set prerender flag if using Hybrid mode
export const prerender = false ;
Query pages in a collection:
import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro" ;
import { CardGrid , LinkCard } from "@astrojs/starlight/components" ;
import Premium from "@components/Premium.astro"
import { getCollection , getEntry } from 'astro:content' ;
const allProtectedPages = await getCollection ( 'protected' );
export const prerender = false ;
< StarlightPage frontmatter = { {
title: "Protected Pages" ,
excerpt: "Protected Pages contains sentisive data, so they are only accessible to users who have been granted with a proper permission."
allProtectedPages . map ( page => (
description = { page . data . excerpt }
href = { "protected/" + page . slug }