Reputation: 150
I was recently playing with new Fresh framework for deno and it was all great but at some point I realized that there is no possibility to add any additional meta data to page head tag. Basically I want to do 2 things:
Do you have any idea how to achieve this? In the ideal world I would want the possibility to provide my own html template, or at least have some flexible way to manipulate provided fixed template. I did find some code snippet in Fresh source file which is basically before-mentioned fixed html template, but unfortunately it doesn't look customizable to me - only variable element would be opts.headComponents, but I'm unsure if I can affect it.
export interface TemplateOptions {
bodyHtml: string;
headComponents: ComponentChildren[];
imports: (readonly [string, string])[];
styles: string[];
preloads: string[];
lang: string;
}
export function template(opts: TemplateOptions): string {
const page = (
<html lang={opts.lang}>
<head>
<meta charSet="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{opts.preloads.map((src) => <link rel="modulepreload" href={src} />)}
{opts.imports.map(([src, nonce]) => (
<script src={src} nonce={nonce} type="module"></script>
))}
<style
id="__FRSH_STYLE"
dangerouslySetInnerHTML={{ __html: opts.styles.join("\n") }}
/>
{opts.headComponents}
</head>
<body dangerouslySetInnerHTML={{ __html: opts.bodyHtml }} />
</html>
);
return "<!DOCTYPE html>" + renderToString(page);
}
Upvotes: 7
Views: 3469
Reputation: 159
You can import the Head
component from fresh and whatever you use as the children will be appended to the <head>
element of the page you return.
// HTML Head to import CSS and fonts
import { Head } from "$fresh/runtime.ts";
It's a bad practice to do this on _app.tsx
because that runs before EVERY request and if you want to have good SEO, you will be setting title, description, and OG tags dynamically, which you only know after your request hits the route you are returning data from.
One option that is an excellent middle ground between both options is creating a DefaultHead
component based on Fresh's Head
, giving it whatever needs to be added to every request and then accepted as props.children whatever you want to set dynamically.
/components/DefaultHead.tsx
//? HTML Head to import CSS and fonts
import { Head } from "$fresh/runtime.ts";
import { toChildArray } from "preact";
import { JSX } from "preact/jsx-runtime";
//? Define required properties for this Head
interface HeadOptions {
title: string;
description: string;
link?: string;
children: JSX.Element | JSX.Element[];
}
//? Creates and exports the Head to be used on all pages
export function DefaultHead(options: HeadOptions) {
return (
<>
<Head>
<title>{options.title}</title>
<meta property="og:title" content={options.title} />
<meta property="og:site_name" content={your-website-name-here} />
<meta property="og:description" content={options.description} />
<meta property="description" content={options.description} />
<meta property="og:type" content="blog" />
<meta property="og:image" content={/path/to/your-website-image-link-for-social-media-here.png}
<meta
property="og:url"
content={options.link}
/>
// You can also import here fonts that are used throughout all the website
<link rel="stylesheet" href="/base.css" /> // Default CSS file that has base styles for every page (example: page font-family, background-color, etc)
{...toChildArray(options.children)} //
</Head>
</>
);
}
For ease of use, this code requires importing toChildArray
so that the props.children is always an array. If you don't want to import this, you will need to manually check it within the <Head>
, or just make sure you are always either passing a single JSX.Element or an array of them.
Then you can import this DefaultHead
on every route and dynamically pass in the properties you want to set:
/routes/literally-any-route.tsx
//? Import DefaultHead with default metadata
import { DefaultHead } from "../components/DefaultHead.tsx";
export default function Home() {
return (
<>
<DefaultHead
title='page title you want to set'
description="page description you want to set"
link="page link you want to show on social media"
>
// put here all the elements you want to add to this <DefaultHead>
<link rel="stylesheet" href="/page.css" />
<link rel="stylesheet" href="/another-page.css" />
</DefaultHead>
// everything else for the body of your response goes here...
</>
);
}
Upvotes: 1
Reputation: 83
You can use the edited answer by @Harrel. However, if you wish to add dynamic content to <head>
on different pages, like <title>
, you can use the <Head>
component in individual pages as well. The children of <Head>
component will stack because that component is implemented as a context as we can see from the source code
You can put <title>
in each page like this
and meta tags and styleshees in _app.tsx
like this
Hope this helps someone.
Upvotes: -1
Reputation: 150
I found the answer in some fresh issue. Edition of head and body tag can be done through JSX, just like this:
/** @jsx h */
import {h} from 'preact';
import {PageProps} from '$fresh/src/server/types.ts';
import {tw} from '@twind';
export const Head = () => (
<head>
<title>My title</title>
</head>
);
export default function LoginPage(props: PageProps) {
return (
<div class={tw`w-screen h-screen flex justify-center bg-green-900`}>
<Head />
</div>
)
}
Guess that way of doing things comes from preact or SSR? From my point of view it's a bit strange - injecting head/body into some divs, not really intuitive.
Edit
Found even better solution, because the answer presented earlier still caused meta tags to be put inside body tag.
We can use _app.tsx which is basically a wrapper for each rendered route and use special Head component from fresh library. And this is the correct way to populate opts.headComponents field from the fixed html template (presented in question). Unfortunately, there seems to be no documentation for that. Example taken from fresh tests:
/** @jsx h */
/** @jsxFrag Fragment */
import { Fragment, h } from "preact";
import { Head } from "$fresh/runtime.ts";
import { AppProps } from "$fresh/server.ts";
export default function App(props: AppProps) {
return (
<>
<Head>
<meta name="description" content="Hello world!" />
</Head>
<props.Component />
</>
);
}
Upvotes: 7