source.File
 }
 type PageMeta struct {
-       WordCount      int
-       FuzzyWordCount int
-       ReadingTime    int
+       wordCount      int
+       fuzzyWordCount int
+       readingTime    int
+       pageMetaInit   sync.Once
        Weight         int
 }
 
        return int64(len(p.rawContent)), nil
 }
 
+func (p *Page) WordCount() int {
+       p.analyzePage()
+       return p.wordCount
+}
+
+func (p *Page) ReadingTime() int {
+       p.analyzePage()
+       return p.readingTime
+}
+
+func (p *Page) FuzzyWordCount() int {
+       p.analyzePage()
+       return p.fuzzyWordCount
+}
+
 func (p *Page) analyzePage() {
-       if p.isCJKLanguage {
-               p.WordCount = 0
-               for _, word := range p.PlainWords() {
-                       runeCount := utf8.RuneCountInString(word)
-                       if len(word) == runeCount {
-                               p.WordCount++
-                       } else {
-                               p.WordCount += runeCount
+       p.pageMetaInit.Do(func() {
+               if p.isCJKLanguage {
+                       p.wordCount = 0
+                       for _, word := range p.PlainWords() {
+                               runeCount := utf8.RuneCountInString(word)
+                               if len(word) == runeCount {
+                                       p.wordCount++
+                               } else {
+                                       p.wordCount += runeCount
+                               }
                        }
+               } else {
+                       p.wordCount = helpers.TotalWords(p.Plain())
                }
-       } else {
-               p.WordCount = len(p.PlainWords())
-       }
 
-       p.FuzzyWordCount = (p.WordCount + 100) / 100 * 100
+               // TODO(bep) is set in a test. Fix that.
+               if p.fuzzyWordCount == 0 {
+                       p.fuzzyWordCount = (p.wordCount + 100) / 100 * 100
+               }
 
-       if p.isCJKLanguage {
-               p.ReadingTime = (p.WordCount + 500) / 501
-       } else {
-               p.ReadingTime = (p.WordCount + 212) / 213
-       }
+               if p.isCJKLanguage {
+                       p.readingTime = (p.wordCount + 500) / 501
+               } else {
+                       p.readingTime = (p.wordCount + 212) / 213
+               }
+       })
 }
 
 func (p *Page) permalink() (*url.URL, error) {
 
 }
 
 func normalizeContent(c string) string {
-       norm := strings.Replace(c, "\n", "", -1)
+       norm := c
+       norm = strings.Replace(norm, "\n", " ", -1)
        norm = strings.Replace(norm, "    ", " ", -1)
        norm = strings.Replace(norm, "   ", " ", -1)
        norm = strings.Replace(norm, "  ", " ", -1)
+       norm = strings.Replace(norm, "p> ", "p>", -1)
+       norm = strings.Replace(norm, ">  <", "> <", -1)
        return strings.TrimSpace(norm)
 }
 
 
        assertFunc := func(t *testing.T, ext string, p *Page) {
                checkPageTitle(t, p, "Simple")
-               checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line. <figure > <img src=\"/not/real\" /> </figure>.\nMore text here.</p><p>Some more text</p>"), ext)
-               checkPageSummary(t, p, "Summary Next Line. . More text here. Some more text", ext)
+               checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line. \n<figure >\n    \n        <img src=\"/not/real\" />\n    \n    \n</figure>\n.\nMore text here.</p>\n\n<p>Some more text</p>\n"))
+               checkPageSummary(t, p, "Summary Next Line.  . More text here. Some more text")
                checkPageType(t, p, "page")
                checkPageLayout(t, p, "page/single.html", "_default/single.html", "theme/page/single.html", "theme/_default/single.html")
        }
        testCommonResetState()
 
        assertFunc := func(t *testing.T, ext string, p *Page) {
-               if p.WordCount != 8 {
-                       t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 8, p.WordCount)
+               if p.WordCount() != 8 {
+                       t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 8, p.WordCount())
                }
        }
 
        viper.Set("HasCJKLanguage", true)
 
        assertFunc := func(t *testing.T, ext string, p *Page) {
-               if p.WordCount != 15 {
-                       t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 15, p.WordCount)
+               if p.WordCount() != 15 {
+                       t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 15, p.WordCount())
                }
        }
-
        testAllMarkdownEnginesForPage(t, assertFunc, "simple", simplePageWithAllCJKRunes)
 }
 
        viper.Set("HasCJKLanguage", true)
 
        assertFunc := func(t *testing.T, ext string, p *Page) {
-               if p.WordCount != 74 {
-                       t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 74, p.WordCount)
+               if p.WordCount() != 74 {
+                       t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 74, p.WordCount())
                }
 
                if p.Summary != simplePageWithMainEnglishWithCJKRunesSummary {
                        t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.plain,
                                simplePageWithMainEnglishWithCJKRunesSummary, p.Summary)
                }
-
        }
 
        testAllMarkdownEnginesForPage(t, assertFunc, "simple", simplePageWithMainEnglishWithCJKRunes)
        viper.Set("HasCJKLanguage", true)
 
        assertFunc := func(t *testing.T, ext string, p *Page) {
-               if p.WordCount != 75 {
-                       t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 74, p.WordCount)
+               if p.WordCount() != 75 {
+                       t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 74, p.WordCount())
                }
 
                if p.Summary != simplePageWithIsCJKLanguageFalseSummary {
                        t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.plain,
                                simplePageWithIsCJKLanguageFalseSummary, p.Summary)
                }
-
        }
 
        testAllMarkdownEnginesForPage(t, assertFunc, "simple", simplePageWithIsCJKLanguageFalse)
 func TestWordCount(t *testing.T) {
 
        assertFunc := func(t *testing.T, ext string, p *Page) {
-               if p.WordCount != 483 {
-                       t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 483, p.WordCount)
+               if p.WordCount() != 483 {
+                       t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 483, p.WordCount())
                }
 
-               if p.FuzzyWordCount != 500 {
-                       t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 500, p.WordCount)
+               if p.FuzzyWordCount() != 500 {
+                       t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 500, p.WordCount())
                }
 
-               if p.ReadingTime != 3 {
-                       t.Fatalf("[%s] incorrect min read. expected %v, got %v", ext, 3, p.ReadingTime)
+               if p.ReadingTime() != 3 {
+                       t.Fatalf("[%s] incorrect min read. expected %v, got %v", ext, 3, p.ReadingTime())
                }
 
                checkTruncation(t, p, true, "long page")