How to set HTML lang attribute dynamically on NextJs Document? - html

I have a multi language site and need to set up the HTML lang attribute according the language for the each page.
I try to pass the value in context, but does not update when page changes.
Here the current code:
import Document, { Html, Head, Main, NextScript } from 'next/document'
import GlobalContext , {eLanguage }from '../components/GlobalContext' //my global context
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
static contextType = GlobalContext;
render() {
console.debug('Started')
console.debug('language:'+ this.context.language)
return (
<Html lang={eLanguage[this.context.language]}> //if the first page loaded as lang 'en' it sets 'en' and apply to all other pages.
<Head>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
Update:
The language of each page can be inferred from the page route

I believe the best solution here is to use a custom ./pages/_document.js file and override the document itself.
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
More explanation can be found here: https://nextjs.org/docs/advanced-features/custom-document

Next 10 supports Internationalized Routing and will add lang dynamically leaving you with:
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>

If you use next/head you can set the language to the html tag. Anything that you pass to the Head component will be either placed in the <head> or <html>.
Next Head works similar to React Helmet, so for your case you could do something along these lines:
Create a component and import Head from "next/head"
Inside the Head tag you add the <html lang={lan} /> to the component.
Then you can pass the desired language to that component, then import the component on the desired pages.
import React from "react"
import Head from "next/head"
const Language = ({title, lang}) => (
<Head>
<html lang={lang} />
<title>{title}</title>
</Head>
)
export default Language
That html bit will be injected inside the <html> tag.
Note that even if we inject it like this the console will log the following error: TypeError: n is null.

I implemented this by adding this to next.config.js file:
i18n: {
// These are all the locales you want to support in
// your application
locales: ['en-US'],
// This is the default locale you want to be used when visiting
// a non-locale prefixed path e.g. `/hello`
defaultLocale: 'en-US' }
I didn't have the need to create a custom _document.js

Create _document.js in `pages`` folder
and use this:
import Document, { Head, Html, Main, NextScript } from 'next/document';
class MyDocument extends Document {
static async getInitialProps(context) {
const initialProps = await Document.getInitialProps(context);
return { ...initialProps };
}
render() {
return (
<Html lang={this.props.locale}>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
for localization use next.config.js
{
i18n: {
locales: ['en', 'fr', 'de',],
defaultLocale: 'en',
},
}

Nextjs versions after 10 provides default localization support,
You don't have to configure much.
It automatically adds lang attribute to html, but still there is no support until now with v12 for dir attribute to include that we can use this small trick inside _document file.
import { Head, Html, Main, NextScript } from "next/document";
function Document(props: any) {
return (
<Html dir={props.__NEXT_DATA__.locale === "en" ? "ltr" : "rtl"}>
<Head></Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
export default Document;
The end result would be

You can use a React useEffect hook to set the document's language without having to change the way that Next.js generates the HTML tag itself.
Within your page component or another appropriate component, include the useEffect hook:
import {useEffect} from "react";
And then add the hook:
const MyPage = () => {
useEffect(() => {
document.documentElement.lang = "en-us";
});
// The rest of your component
}
This passes Lighthouse's check for "hreflang", and if your site has multiple languages, you can use this to set the page language per page.

You can add the lang attribute without creating _document.js
All you need to do is add this code to your next.config.js:
i18n: {
locales: ['en'],
defaultLocale: 'en',
},

Using document object the lang attribute can be set like this:
var language= ...
switch (language) {
case en: document.documentElement.lang = 'en-us'; break;
...
}
This lang attribute will not be set on the initial html, before page is hydrated, but will still pass chrome "hreflang" audit check.

Most answers assume that the lang attribute is on the html tag is an alternative to link with a hreflang attribute. However, this is not the case!
You should provide both, as they are complementary.
This is how I implemented my hreflang links in NEXT.JS:
export const LayoutController = () => {
const router = useRouter();
return (
<Layout>
<Head>
{router.locales.map((locale) => (
<link
key={locale}
rel="alternate"
href={`${process.env.NEXT_PUBLIC_BASE_URL}${
locale === DEFAULT_LOCALE ? '' : `/${locale}`
}${router.asPath}`}
hrefLang={locale}
/>
))}
</Head>
{children}
</Layout>
);
};
These links were generated in my html page:

To overide default nextjs settings create the file ./pages/_document.js and extend the Document class as shown below:
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
Custom attributes are allowed as props, like lang:
<Html lang="en">
You can read more about custom document here: https://nextjs.org/docs/advanced-features/custom-document

Related

Is it possible to insert html into the <Head> section of NextJS?

I have a backend that returns meta tags for a specific page. I would like to insert this response into the Head section of my page component.
I can insert html into React using <div dangerouslySetInnerHTML={{__html: response.someHtml}} />.
Is it possible to do the same for the <Head> component?
You can create a BasePage component and render the Head component inside. This BasePage component will wrap up all the pages.
import HeadTags from "./Head";
const BasePage = (props) => {
const {
// you might have more props but those are related to the Heads
children,
title,
metaDescription,
} = props;
return (
<>
<HeadTags
title={title}
metaDescription={metaDescription}
canonicalPath={canonicalPath}
></HeadTags>
// you mightadd more logic here
</>
);
};
You can define HeadTags components
const HeadTags = (props) => {
const {
title = "Default tile",
metaDescription = "default metaDescriptino",
} = props;
return (
<Head>
<title>{title}</title>
{/* mobile devices by default takes the content from desktop and squueze it. But if you want it to be responsive */}
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" key="description" content={metaDescription} />
// you could add more tags
</Head>
);
};
Now since your page is wrapped by BasePage, you can pass the props to BasePage and it will pass it to HeadTags component so you will set meta data dynamically based on your backend returning metatags.

How to add lang attribute to html tag in Next.js?

After running some performance check on my Next.js portfolio site I noticed that the main index.html is missing a lang attribute - which gets returned as a deduction from the accessibility score.
I can add the locale by using the i18n setup to next.config.js, but those features are incompatible with next export - the site is statically generated.
Error: i18n support is not compatible with next export. See here for more info on deploying: https://nextjs.org/docs/deployment
Are there any other ways to add the lang attribute?
You can add the lang attribute to the <Html> tag in your custom _document.
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
As alternative to #juliomalves answer, next.config.js file can also be used to define lang.
create a file called "next.config.js" in the root of your project with the below,
module.exports = {
i18n: {
locales: ["en"],
defaultLocale: "en",
},
};
ref: https://nextjs.org/docs/api-reference/next.config.js/introduction

How to use Custom Elements with template? [duplicate]

This question already has an answer here:
Nested element (web component) can't get its template
(1 answer)
Closed 3 years ago.
I was trying to understand how web components work so I tried to write a small app that I served on a webserver (tested on Chrome which support rel="import"):
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="import" href="my-app.html" />
</head>
<body>
<my-app />
</body>
</html>
my-app.html:
<template id="template">
<div>Welcome to my app!</div>
</template>
<script>
class MyApp extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: "open"});
const template = document.getElementById("template");
const clone = document.importNode(template.content, true);
shadow.appendChild(clone);
}
}
customElements.define("my-app", MyApp);
</script>
But it doesn't seem to work. The <my-app /> tag is not rendered at all in the DOM and I get this error on the console:
Uncaught TypeError: Cannot read property 'content' of null
What cannot I retrieve the template node? What am I doing wrong?
What I would also like to know is if I am allowed to write an HTML document without the boilerplate code (doctype, head, body, ...), because it's meant to describe a component and not an entire document to be used as is. Is it allowed by the HTML5 specs and/or is it correctly interpreted by a majority of browsers?
Thank you for your help.
While inside the template, don't use the document global:
<template id="template">
<div>Welcome to my app!</div>
</template>
<script>
class MyApp extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: "open"});
// while inside the imported HTML, `currentDocument` should be used instead of `document`
const currentDocument = document.currentScript.ownerDocument;
// notice the usage of `currentDocument`
const template = currentDocument.querySelector('#template');
const clone = document.importNode(template.content, true);
shadow.appendChild(clone);
}
}
customElements.define("my-app", MyApp);
</script>
Plunker demo: https://plnkr.co/edit/USvbddEDWCSotYrHic7n?p=preview
PS: Notes com compatibility here, though I assume you know HTML imports are to be deprecated very soon.

How can I link my directive to html from the typescript class?

I am working on a angular directive POC. Here I have an HTML page and a controller class. I have method in my typescript class that returns a directive. Also I have a placeholder for this directive in the HTML page. How can I link these two.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>TypeScript HTML App</title>
<link rel="stylesheet" href="app.css" type="text/css" />
<script src="scripts/angular.js"></script>
<script data-main="main" src="scripts/require.js" type="text/javascript"></script>
</head>
<body ng-controller="TypeScriptController as TSCtrl">
<helloworld> </helloworld>
</body>
</html>
/////controller class
export class TypeScriptController {
name: string;
place: string;
output: string;
text: string;
helloworld: ng.IDirective;
protected ngModule: ng.IModule = null;
constructor() {
this.name = "Obama";
this.place = "America";
this.text = "welcome";
this.helloworld = this.getCustomDirective();
//how can link this helloworld directive to the html
}
getCustomDirective(): ng.IDirective {
return {
restrict: 'E',
replace: true,
controller: [TypeScriptController],
controllerAs: "TSCtrl",
//templateUrl: "first.html"
template: '<h1>{{TSCtrl.place}}</h1>'
};
}
}
angular.element(document).ready(() => {
var main: TypeScriptController = new TypeScriptController();
});
How can I link the helloworld directive the html. I don't have a "ng-app" name to do something like..
angular.module('myApp').directive('myDirective', myDirective);
I want to register the directive from inside the class not outside it..I want to create module or something similar inside the class and then use it to register the directive.
How can I link these two.
Not without creating an angular.module. Controllers are looked up from window but directive's are not. Also if you don't have an ng-app (or bootstrap) then Angular will not do directive tag -> directive transform for that section of html.

AngularJS trouble adding page titles

I am writing a web app using AngularJS. It is not a single page application but all my codes are in one file- index.ejs.
So my set up for index.ejs roughly looks like this:
<head>
<title> Main page </title>
</head>
<body>
<div class="row">
<div class="col-md-10 col-md-offset-1">
<ui-view></ui-view>
</div>
</div>
<script type = "text/ng-template" id = "/main.html">
.....
</script>
<script type = "text/ng-template" id = "/addStuff.html">
.....
</script>
<script type = "text/ng-template" id = "/searchStuff.html">
.....
</script>
<script type = "text/ng-template" id = "/about.html">
.....
</script>
</body>
I have a title for the main page on top of index.ejs. I would also like to have seperate titles for each page so when they are opened in a new tab, I know which one is which. I have tried doing:
<script type = "text/ng-template" id = "/addStuff.html">
<head>
<title> Add Stuff </title>
</head>
.....
But this doesn't work. Any ideas? Thanks.
You should use ui-router. In which case you can add a top level controller on the body or html element, for example <html ng-app="my-app" ng-controller="AppCtrl">
And add a '$stateChangeSuccess' listener whenever a new route is loaded...
.controller('AppCtrl', function AppCtrl($scope) {
$scope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
if (angular.isDefined(toState.data.pageTitle)) {
$scope.pageTitle = toState.data.pageTitle;
}
});
})
Then in the route definition you can add a property called data.pageTitle
$stateProvider.state( 'profile', {
url: '/profile',
views: {
"main": {
controller: 'ProfileCtrl',
templateUrl: 'profile/profile.tpl.html'
}
},
data:{
pageTitle: 'Profile'
}
})
Then in your main index.html file, you can bind the pageTitle attribute:
<title ng-bind="pageTitle"></title>
The most important part is the you move the directive ng-app on the <html> tag if it's not already in there.
Thus all the html page is covered by angular.
Eg:
<html ng-app="app">
...
</html>
Then it's really your choice.
You can use a custom directive to wrap the title, generate a service or just use the {{ }} syntax.