HTML vs Markdown files

Based on what I’ve read in the documentation there is no way to apply semantic meaning to an MD file. For example, if I wanted to place a <section> tag it is not possible to do inline knowing that we cannot wrap MD in HTML tags.

I love the simplicity of MD, but I’m finding I need to do a little more than what is provided in MD. Should I use HTML files in my content folder instead of MD knowing that I need more?

Does anyone else us HTML over MD for flexibility? Are there any drawbacks to using HTML knowing that I can still use shortcodes in HTML?

You can wrap an HTML element tag, like <section>, around Markdown if you separate each tag from the Markdown with an empty line:

<section>

**Here** is my [Markdown](https://daringfireball.net/projects/markdown).

</section>

Not sure whether this meets your requirements, however.

@bwintx that definitely helps with the semantic tag issue in the markdown. So simple it’s silly. I tried adding the tags but without the extra space at first. I didn’t realize you needed that space. Thank you!

Any thoughts on using an HTML file over Markdown? I can see where I might have some issues when I want to format pages differently or add a new pattern (i.e. extra columns for a row of cards), and it seems that HTML would be easier than trying to come up with a shortcode solution. Would you try to use shortcodes for everything, mix HTML into your MD file, or just us an HTML file?

1 Like

Glad to help!

It definitely sounds like a case for more layouts (perhaps partials, as in the columns-rendering you mentioned) and/or shortcodes, assuming you’re comfortable keeping the content in Markdown format.

However, you certainly can put as much as you wish in HTML, if you find that simpler. For example, my home page, posts lists, and sitemap are completely HTML (with some templating, of course), while the other content is Markdown rendered within layouts.

It’s all a matter of what’s more convenient from a standpoint of content management.

Does this work with divs? I feel like I’ve tried this with div before and the interior markdown is not rendered.

Just now tried it locally and, yes, as long as you keep the extra line spaces, it works. Don’t know whether earlier versions had a problem but it works with Hugo 0.102.3.

Wow that did the trick! Thanks. Pretty sure I can close this issue now Incompatibility with Either Galleries or a Photo Page · Issue #3 · jsonbecker/plugin-glightbox · GitHub.

1 Like

@bwintx Thank again for you help. You’ve answered a lot of my questions during my Hugo journey, some on your website, and I really appreciate.

Any security concerns with using HTML in MD? I had to change the renderer to unsafe. Is that ok considering the final output is all HTML?

1 Like

@burtcrismore Well, that’s the setting I use, and for that reason. I should note that I have a fairly tight Content Security Policy on my site, so am hoping that compensates adequately. You might want to think about a CSP, too, in case you haven’t already done so.

Again, glad if I can be of any help, and thank you for the kind words.

The unsafe has more to do with when you have content editors other than yourself. In that case a content editor could insert malicious HTML / JavaScript (via attributes) into your site.

1 Like

@cshoredaniel that’s good to know. Thank you.

@bwintx thanks for the heads up on CSP. I’ll have to read up on that.

I’ve also seen some people us a “raw html” shortcode which seems like a good option.

1 Like

This worked for me but I did not have any javascript in my site. You need to put this in your header using a function or worker.

Think Lambda function, Cloudflare worker, GCP has them… all the major hosting players have them.

addEventListener(‘fetch’, event => {
event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {

let originalResponse = await fetch(request)

// pass in the original response so we can modify some of it.
let response = new Response(originalResponse.body,originalResponse);

response.headers.set(‘X-Frame-Options’, ‘SAMEORIGIN’);
response.headers.set(‘Strict-Transport-Security’, ‘max-age=31536000; includeSubdomains; preload’);
response.headers.set(‘Referrer-Policy’,‘same-origin’);
response.headers.set(‘Content-Security-Policy’, ‘default-src 'self'’);
response.headers.set(‘Feature-Policy’,‘camera 'none'; geolocation 'none'’);
response.headers.set(‘X-Content-Type-Options’,‘nosniff’);
response.headers.set(‘X-XSS-Protection’,‘1; mode=block’);
response.headers.set(‘Permissions-Policy’, ‘camera=(), geolocation=(), microphone=()’);
return response
}

I used this in a cloudflare worker.

Start here and generate one Report URI: Generate your Content Security Policy and implement.

1 Like

Agreed. I use a CFW not only for my CSP (including insertion of nonces in scripts and styles) but also caching static assets. Very helpful.

1 Like

I was trying to use nonce to inline my css. I had an issue with unsafe inline. Can this be achieved with an MD5 hash or a hash created by blockchain?

Here’s a repo with an older version of my CFW. (Much of it shamelessly borrowed :slight_smile:)

(Sorry for the switcheroo; earlier link was to a private repo.)

You can see how the replace statements can be used to add nonces. For example, in a newer version of this code, I have:

    .replace(
      'rel="stylesheet"',
      `rel="stylesheet" nonce="${nonce}"`
    )

Shoulders of giants. Die before you quit.

1 Like

All my html is in my layouts and partials, content is in Markdown with html thrown in when Markdown falls short, flow content like <abbr title="Hyper Text Markup Language"> or <q cite="Some guy">, for example.

I can’t imagine a situation where I’d ever want to put sectioning content like <section> inside an <article>. My list layouts are <sections> containing a load of <article>s and my single layouts are <articles>, layout and content are entirely separate. If I want to format a particular section differently, I’ll use a different layout.

This is the ideal and most semantically correct way to go, yes. However, I think the earlier questions were whether Hugo supports inserting HTML in Markdown when, for some reason (perhaps due to time constraints during a rushed project), that should become unavoidable — as opposed to doing so as a matter of habit.

1 Like

I tried this one out today. Let me know if you see any errors.

addEventListener(‘fetch’, event => {
event.respondWith(handleRequest(event.request))
})

function dec2hex(dec) {
return (“0” + dec.toString(16)).substr(-2)
}

function generateNonce() {
const arr = new Uint8Array(12)
crypto.getRandomValues(arr)
const values = Array.from(arr, dec2hex)
return [
btoa(values.slice(0, 5).join(“”)).substr(0, 14),
btoa(values.slice(5).join(“”)),
].join(“/”)
}

/**

  • Respond to the request
  • @param {Request} request
    */

async function handleRequest(request) {
const nonce = generateNonce()
let response = await fetch(request)

let imageResponse = await fetch(request)
let type = imageResponse.headers.get(“Content-Type”) || “”
if (!type.startsWith(“text/”)) {
// Not text. Don’t modify.
let newHeaders = new Headers(imageResponse.headers)
newHeaders.set(“Cache-Control”, “public, max-age=2678400, immutable”)
newHeaders.set(“CDN-Cache-Control”, “public, max-age=2678400, immutable”)
newHeaders.set(“x-BW-test”, “Non-text item - headers edited!”)
// newHeaders.set(“Permissions-Policy”, “interest-cohort=()”)
newHeaders.set(“Strict-Transport-Security”, “max-age=63072000; includeSubDomains; preload”)
newHeaders.set(“X-Frame-Options”, “SAMEORIGIN”)
newHeaders.set(“X-Content-Type-Options”, “nosniff”)
newHeaders.set(“Referrer-Policy”, “no-referrer, strict-origin-when-cross-origin”)
newHeaders.set(“Content-Security-Policy”, “default-src ‘strict-dynamic’ https://perfectlighthousescores.com”)
newHeaders.set(“Permissions-Policy”, “geolocation=(), microphone=(), battery=(), accelerometer=(), camera=(), gyroscope=(), magnetometer=(), payment=(), usb=(), interest-cohort=()”)
newHeaders.set(“cf-nonce-generator”, “HIT”)
newHeaders.set(“Expect-CT”, “enforce max-age=30 report-uri=Report URI: Welcome to report-uri.com”)
newHeaders.set(“Cross-Origin-Embedder-Policy”, “require-corp; report-to=default”)
newHeaders.set(“Cross-origin-opener-policy-report-only”, “same-origin; report-to=default”)
newHeaders.set(“cross-origin-resource-policy”, “same-origin”)
newHeaders.set(“Cross-Origin-Opener-Policy”, “same-origin”)
return new Response(imageResponse.body, {
status: imageResponse.status,
statusText: imageResponse.statusText,
headers: newHeaders
})
}

const html = (await response.text())
.replace(/DhcnhD3khTMePgXw/gi, nonce)
.replace(
‘src="https://ajax.cloudflare.com’,
nonce="${nonce}" src="https://ajax.cloudflare.com
)
.replace(
‘src="https://static.cloudflareinsights.com’,
nonce="${nonce}" src="https://static.cloudflareinsights.com
)
.replace(
‘cloudflare-static/email-decode.min.js"’,
cloudflare-static/email-decode.min.js" nonce="${nonce}"
)

let ttl = undefined
let cache = caches.default
let url = new URL(request.url)
let shouldCache = false
let jsStuff = false
let svgStuff = false

const filesRegex = /(..(ac3|avi|bmp|br|bz2|css|cue|dat|doc|docx|dts|eot|exe|flv|gif|gz|ico|img|iso|jpeg|jpg|js|json|map|mkv|mp3|mp4|mpeg|mpg|ogg|pdf|png|ppt|pptx|qt|rar|rm|svg|swf|tar|tgz|ttf|txt|wav|webp|webm|webmanifest|woff|woff2|xls|xlsx|xml|zip))$/
const jsRegex = /(.
.(js))$/
const svgRegex = /(.*.(svg))$/

if (url.pathname.match(filesRegex)) {
shouldCache = true
ttl = 31536000
}
if (url.pathname.match(jsRegex)) {
jsStuff = true
}
if (url.pathname.match(svgRegex)) {
svgStuff = true
}

let newHeaders = new Headers(response.headers)
newHeaders.set(“Cache-Control”, “public, max-age=0”)
// newHeaders.set(“Permissions-Policy”, “interest-cohort=()”)
newHeaders.set(“Strict-Transport-Security”, “max-age=63072000; includeSubDomains; preload”)
newHeaders.set(“X-Frame-Options”, “SAMEORIGIN”)
newHeaders.set(“X-Content-Type-Options”, “nosniff”)
newHeaders.set(“Referrer-Policy”, “no-referrer, strict-origin-when-cross-origin”)
newHeaders.set(“Content-Security-Policy”, “default-src ‘strict-dynamic’ https://perfectlighthousescores.com”)
newHeaders.set(“Permissions-Policy”, “geolocation=(), microphone=(), accelerometer=(), battery=(), camera=(), gyroscope=(), magnetometer=(), payment=(), usb=(), interest-cohort=()”)
newHeaders.set(“Expect-CT”, “enforce max-age=30 report-uri=Report URI: Welcome to report-uri.com”)
newHeaders.set(“Cross-Origin-Embedder-Policy”, “require-corp; report-to=default”)
newHeaders.set(“Cross-origin-opener-policy-report-only”, “same-origin; report-to=default”)
newHeaders.set(“cross-origin-resource-policy”, “same-origin”)
newHeaders.set(“Cross-Origin-Opener-Policy”, “same-origin”)
if (ttl) {
newHeaders.set(“Cache-Control”, “public, max-age=” + ttl + “, immutable”)
newHeaders.set(“CDN-Cache-Control”, “public, max-age=” + ttl + “, immutable”)
} else {
newHeaders.set(“Content-Security-Policy-Report-Only”, report-uri https://perfectlighthousescores.report-uri.com/r/d/csp/enforce; default-src 'strict-dynamic' https://perfectlighthousescores.com; connect-src 'self' https://perfectlighthousescores.com https://cloudflareinsights.com; base-uri 'self' https://perfectlighthousescores.com; frame-src 'self' https://perfectlighthousescores.com; frame-ancestors 'self' https://perfectlighthousescores.com; form-action 'self'; style-src 'self' https://perfectlighthousescores.com; style-src-attr 'self' https://perfectlighthousescores.com; img-src 'self' https://perfectlighthousescores.com; font-src 'self' https://perfectlighthousescores.com; script-src 'nonce-${nonce}' 'strict-dynamic' https: 'self'; script-src-elem 'self' https://perfectlighthousescores.com 'nonce-${nonce}')
newHeaders.set(“Report-To”, “{‘group’:‘default’,‘max_age’:31536000,‘endpoints’:[{‘url’:‘Report URI: Welcome to report-uri.com’:true}”)
newHeaders.set(“X-XSS-Protection”, “1”)
}
newHeaders.set(“cf-nonce-generator”, “HIT”)
if (jsStuff) {
newHeaders.set(“Content-Type”, “application/javascript; charset=utf-8”)
}
if (svgStuff) {
newHeaders.set(“Content-Type”, “image/svg+xml; charset=utf-8”)
}

return new Response(html, {
status: response.status,
statusText: response.statusText,
headers: newHeaders
})
}

I’m no expert on these. If you’re running into a specific error, I might have an idea based on my own glitches over time but, other than that, :person_shrugging: